diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 061e0c0cdf..d6647ee3ec 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4068,6 +4068,39 @@ export function SalesforceIcon(props: SVGProps) { ) } +export function SapS4HanaIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + ) +} + export function ServiceNowIcon(props: SVGProps) { return ( @@ -4683,9 +4716,16 @@ export function IAMIcon(props: SVGProps) { export function IdentityCenterIcon(props: SVGProps) { return ( - + + + + + + + + diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 13061ba781..cee85ce77e 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -153,6 +153,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SapS4HanaIcon, SESIcon, SearchIcon, SecretsManagerIcon, @@ -367,6 +368,7 @@ export const blockTypeToIconMap: Record = { rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, + sap_s4hana: SapS4HanaIcon, search: SearchIcon, secrets_manager: SecretsManagerIcon, sendgrid: SendgridIcon, diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 91d84fa1e9..b2effa9d89 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -149,6 +149,7 @@ "rootly", "s3", "salesforce", + "sap_s4hana", "search", "secrets_manager", "sendgrid", diff --git a/apps/docs/content/docs/en/tools/sap_s4hana.mdx b/apps/docs/content/docs/en/tools/sap_s4hana.mdx new file mode 100644 index 0000000000..a8c07c1524 --- /dev/null +++ b/apps/docs/content/docs/en/tools/sap_s4hana.mdx @@ -0,0 +1,1182 @@ +--- +title: SAP S/4HANA +description: Read and write SAP S/4HANA Cloud business data via OData +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[SAP S/4HANA](https://www.sap.com/products/erp/s4hana.html) is SAP's flagship intelligent ERP suite, running on the in-memory HANA database. It powers finance, supply chain, procurement, sales, and manufacturing for organizations of every size, and exposes its business data through a broad catalog of OData services on SAP Business Technology Platform (BTP). + +With SAP S/4HANA, you can: + +- **Run core business processes**: Manage finance, procurement, sales, logistics, inventory, and manufacturing on a single source of truth. +- **Model master data at scale**: Maintain business partners, customers, suppliers, products, and organizational structures across multiple company codes, sales organizations, and plants. +- **Execute transactional flows end to end**: Create and update sales orders, purchase requisitions, purchase orders, deliveries, billing documents, supplier invoices, and stock movements with full audit trails. +- **Govern access cleanly**: Use Communication Arrangements, Communication Systems, and Communication Scopes to scope OAuth client credentials to exactly the services each integration needs. +- **Integrate via standard OData**: Every entity supported here speaks OData v2 with consistent paging, filtering, expansion, and ETag-based optimistic concurrency. + +In Sim, the SAP S/4HANA integration lets your agents read and write directly against your tenant's OData services using per-tenant OAuth 2.0 client credentials. Agents can list and fetch master data, create and update transactional documents, run stock and material document queries, and execute arbitrary OData v2 calls against any whitelisted Communication Scenario — all routed through a single internal proxy that handles token acquisition, CSRF fetch-and-retry, and OData error normalization. Use it to automate order-to-cash, procure-to-pay, and inventory workflows, keep SAP in sync with the rest of your stack, or trigger downstream agent logic from SAP business events. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +{/* MANUAL-CONTENT-START:usage */} +Connect any SAP S/4HANA tenant — **Cloud Public Edition**, **Cloud Private Edition (RISE)**, or **on-premise** — and read or write business data through the official OData v2 services. Each tool routes through a single internal proxy that handles token acquisition, CSRF fetch-and-retry for write operations, and OData error normalization. + +### Deployment modes + +Pick the deployment that matches your tenant in the **Deployment** dropdown: + +- **S/4HANA Cloud Public Edition** — provide your **BTP subaccount subdomain** and **region** (e.g., `eu10`, `us10`). The host is derived automatically as `{subdomain}-api.s4hana.ondemand.com`, and OAuth tokens are fetched from the matching BTP UAA endpoint. Authentication is OAuth 2.0 client credentials configured in a Communication Arrangement. +- **S/4HANA Cloud Private Edition (RISE)** — provide your **OData Base URL** (e.g., `https://my-tenant.s4hana.cloud.sap`). Authenticate with **OAuth 2.0 client credentials** (provide the tenant's UAA `tokenUrl`, `clientId`, `clientSecret`) or **HTTP Basic** with a Communication User (`username`, `password`). +- **On-premise S/4HANA** — provide your **OData Base URL** (e.g., `https://sap.internal.company.com:44300`). Authenticate with **OAuth 2.0 client credentials** issued by your on-prem identity provider, or **HTTP Basic** with a service user. + +### What you can do + +Read and create business partners, customers, suppliers, sales orders, deliveries (inbound/outbound), billing documents, products, stock and material documents, purchase requisitions, purchase orders, and supplier invoices. Update business partners, customers, suppliers, products, sales orders, purchase orders, and purchase requisitions with PATCH. Run arbitrary OData v2 queries against any whitelisted Communication Scenario or registered service. + +### Optimistic concurrency + +All update tools accept an optional `ifMatch` ETag. When omitted, `If-Match` defaults to a wildcard (unconditional). For safe concurrent updates, fetch the entity first, capture its ETag from the response, and pass it as `ifMatch` to detect lost updates. +{/* MANUAL-CONTENT-END */} + + +Connect SAP S/4HANA Cloud Public Edition with per-tenant OAuth 2.0 client credentials configured in your Communication Arrangements. Read and create business partners, customers, suppliers, sales orders, deliveries (inbound/outbound), billing documents, products, stock and material documents, purchase requisitions, purchase orders, and supplier invoices, or run arbitrary OData v2 queries against any whitelisted Communication Scenario. + + + +## Tools + +### `sap_s4hana_list_business_partners` + +List business partners from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "BusinessPartnerCategory eq \'1\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \($expand\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_BusinessPartner entities | + +### `sap_s4hana_get_business_partner` + +Retrieve a single business partner by BusinessPartner key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `businessPartner` | string | Yes | BusinessPartner key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \($expand\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_BusinessPartner entity | + +### `sap_s4hana_create_business_partner` + +Create a business partner in SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner). For Person category 1 provide FirstName and LastName. For Organization category 2 provide OrganizationBPName1. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `businessPartnerCategory` | string | Yes | BusinessPartnerCategory: "1" Person, "2" Organization, "3" Group | +| `businessPartnerGrouping` | string | Yes | BusinessPartnerGrouping \(number range / role grouping configured in S/4HANA, e.g. "0001"\) | +| `firstName` | string | No | FirstName \(required for Person\) | +| `lastName` | string | No | LastName \(required for Person\) | +| `organizationBPName1` | string | No | OrganizationBPName1 \(required for Organization\) | +| `body` | json | No | Optional additional A_BusinessPartner fields merged into the create payload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Created A_BusinessPartner entity | + +### `sap_s4hana_update_business_partner` + +Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `businessPartner` | string | Yes | BusinessPartner key to update \(string, up to 10 characters\) | +| `body` | json | Yes | JSON object with A_BusinessPartner fields to update \(e.g., \{"FirstName":"Jane","SearchTerm1":"VIP"\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_BusinessPartner entity if SAP returns one | + +### `sap_s4hana_list_customers` + +List customers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "CustomerAccountGroup eq \'Z001\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_CustomerCompany,to_CustomerSalesArea"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_Customer entities | + +### `sap_s4hana_get_customer` + +Retrieve a single customer by Customer key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `customer` | string | Yes | Customer key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_CustomerCompany,to_CustomerSalesArea"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_Customer entity | + +### `sap_s4hana_update_customer` + +Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. Note: API_BUSINESS_PARTNER limits A_Customer PATCH to a small set of modifiable fields (e.g., OrderIsBlockedForCustomer, DeliveryIsBlock, BillingIsBlockedForCustomer, PostingIsBlocked, DeletionIndicator). Company-code attributes like PaymentBlockingReason live on A_CustomerCompany. Most descriptive customer attributes are read-only here and must be updated via the BusinessPartner entity. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `customer` | string | Yes | Customer key to update \(string, up to 10 characters\) | +| `body` | json | Yes | JSON object with A_Customer fields to update \(e.g., \{"OrderIsBlockedForCustomer":true,"DeletionIndicator":false\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_Customer entity if SAP returns one | + +### `sap_s4hana_list_suppliers` + +List suppliers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "SupplierAccountGroup eq \'BP02\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_SupplierCompany,to_SupplierPurchasingOrg"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_Supplier entities | + +### `sap_s4hana_get_supplier` + +Retrieve a single supplier by Supplier key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `supplier` | string | Yes | Supplier key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_SupplierCompany,to_SupplierPurchasingOrg"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_Supplier entity | + +### `sap_s4hana_update_supplier` + +Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. Note: API_BUSINESS_PARTNER limits A_Supplier PATCH to a small set of modifiable fields (e.g., PostingIsBlocked, PurchasingIsBlocked, PaymentIsBlockedForSupplier, DeletionIndicator, SupplierAccountGroup). Company-code fields like PaymentBlockingReason live on A_SupplierCompany. Most descriptive supplier attributes are read-only here and must be updated via the BusinessPartner entity. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `supplier` | string | Yes | Supplier key to update \(string, up to 10 characters\) | +| `body` | json | Yes | JSON object with A_Supplier fields to update \(e.g., \{"PaymentIsBlockedForSupplier":true,"PostingIsBlocked":true\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_Supplier entity if SAP returns one | + +### `sap_s4hana_list_sales_orders` + +List sales orders from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "SalesOrganization eq \'1010\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_SalesOrder entities | + +### `sap_s4hana_get_sales_order` + +Retrieve a single sales order by SalesOrder key from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `salesOrder` | string | Yes | SalesOrder key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_SalesOrder entity | + +### `sap_s4hana_create_sales_order` + +Create a sales order in SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with deep insert of sales order items via to_Item. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `salesOrderType` | string | Yes | SalesOrderType \(e.g., "OR" Standard Order\) | +| `salesOrganization` | string | Yes | SalesOrganization \(4 chars, e.g., "1010"\) | +| `distributionChannel` | string | Yes | DistributionChannel \(2 chars, e.g., "10"\) | +| `organizationDivision` | string | Yes | OrganizationDivision \(2 chars, e.g., "00"\) | +| `soldToParty` | string | Yes | SoldToParty business partner key \(up to 10 chars\) | +| `items` | json | Yes | Array of sales order items for to_Item deep insert. Each item should include Material and RequestedQuantity \(e.g., \[\{"Material":"TG11","RequestedQuantity":"1"\}\]\). | +| `body` | json | No | Optional additional A_SalesOrder fields merged into the create payload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Created A_SalesOrder entity \(with deep-inserted items if expanded by SAP\) | + +### `sap_s4hana_update_sales_order` + +Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `salesOrder` | string | Yes | SalesOrder key to update \(string, up to 10 characters\) | +| `body` | json | Yes | JSON object with A_SalesOrder fields to update \(e.g., \{"PurchaseOrderByCustomer":"PO-12345","HeaderBillingBlockReason":"01"\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_SalesOrder entity if SAP returns one | + +### `sap_s4hana_delete_sales_order` + +Delete an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Only orders without subsequent documents (deliveries, invoices) can be deleted; otherwise reject items via update instead. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `salesOrder` | string | Yes | SalesOrder key to delete \(string, up to 10 characters\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on successful deletion | + +### `sap_s4hana_list_outbound_deliveries` + +List outbound deliveries from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "OverallDeliveryStatus eq \'C\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_DeliveryDocumentItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_OutbDeliveryHeader entities | + +### `sap_s4hana_get_outbound_delivery` + +Retrieve a single outbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `deliveryDocument` | string | Yes | DeliveryDocument key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_DeliveryDocumentItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_OutbDeliveryHeader entity | + +### `sap_s4hana_list_inbound_deliveries` + +List inbound deliveries from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "ReceivingPlant eq \'1010\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_DeliveryDocumentItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_InbDeliveryHeader entities | + +### `sap_s4hana_get_inbound_delivery` + +Retrieve a single inbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `deliveryDocument` | string | Yes | DeliveryDocument key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_DeliveryDocumentItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_InbDeliveryHeader entity | + +### `sap_s4hana_list_billing_documents` + +List billing documents (customer invoices) from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "SoldToParty eq \'10100001\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_BillingDocument entities | + +### `sap_s4hana_get_billing_document` + +Retrieve a single billing document (customer invoice) by BillingDocument key from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `billingDocument` | string | Yes | BillingDocument key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_BillingDocument entity | + +### `sap_s4hana_list_products` + +List products (materials) from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "ProductType eq \'FERT\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \($expand\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_Product entities | + +### `sap_s4hana_get_product` + +Retrieve a single product (material) by Product key from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `product` | string | Yes | Product key \(string, up to 40 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Description"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_Product entity | + +### `sap_s4hana_update_product` + +Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PATCH only sends the fields you provide; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV PATCH/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `product` | string | Yes | Product key to update \(string, up to 40 characters\) | +| `body` | json | Yes | JSON object with A_Product fields to update \(e.g., \{"ProductGroup":"L001","IsMarkedForDeletion":false\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_Product entity if SAP returns one | + +### `sap_s4hana_list_material_stock` + +List material stock quantities from SAP S/4HANA Cloud (API_MATERIAL_STOCK_SRV, A_MatlStkInAcctMod). The entity uses an 11-field composite key (Material, Plant, StorageLocation, Batch, Supplier, Customer, WBSElementInternalID, SDDocument, SDDocumentItem, InventorySpecialStockType, InventoryStockType) — query with $filter on these fields instead of a direct key lookup. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., \"Material eq 'TG10' and Plant eq '1010' and InventoryStockType eq '01'\"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \($expand\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_MatlStkInAcctMod stock entries | + +### `sap_s4hana_list_material_documents` + +List material document headers (goods movements) from SAP S/4HANA Cloud (API_MATERIAL_DOCUMENT_SRV, A_MaterialDocumentHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., \"MaterialDocumentYear eq '2024' and PostingDate ge datetime'2024-01-01T00:00:00'\"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_MaterialDocumentItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_MaterialDocumentHeader entities | + +### `sap_s4hana_list_purchase_requisitions` + +List purchase requisitions from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "PurchaseRequisitionType eq \'NB\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_PurchaseReqnItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_PurchaseRequisitionHeader entities | + +### `sap_s4hana_get_purchase_requisition` + +Retrieve a single purchase requisition by PurchaseRequisition key from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `purchaseRequisition` | string | Yes | PurchaseRequisition key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_PurchaseReqnItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_PurchaseRequisitionHeader entity | + +### `sap_s4hana_create_purchase_requisition` + +Create a purchase requisition in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). PurchaseRequisition is auto-assigned by SAP from the document number range; provide line items via the to_PurchaseReqnItem deep-insert array. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `purchaseRequisitionType` | string | Yes | PurchaseRequisitionType \(e.g., "NB" Standard PR\) | +| `items` | json | Yes | to_PurchaseReqnItem deep-insert array \(e.g., \[\{"PurchaseRequisitionItem":"10","Material":"TG11","RequestedQuantity":"5","Plant":"1010","BaseUnit":"PC","DeliveryDate":"/Date\(1735689600000\)/"\}\]\) | +| `body` | json | No | Additional A_PurchaseRequisitionHeader fields merged into the create payload \(e.g., \{"PurchaseRequisitionDescription":"Office supplies"\}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Created A_PurchaseRequisitionHeader entity | + +### `sap_s4hana_update_purchase_requisition` + +Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `purchaseRequisition` | string | Yes | PurchaseRequisition key to update \(string, up to 10 characters\) | +| `body` | json | Yes | JSON object with A_PurchaseRequisitionHeader fields to update \(e.g., \{"PurchaseRequisitionType":"NB"\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_PurchaseRequisitionHeader entity if SAP returns one | + +### `sap_s4hana_list_purchase_orders` + +List purchase orders from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "CompanyCode eq \'1010\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_PurchaseOrderItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_PurchaseOrder entities | + +### `sap_s4hana_get_purchase_order` + +Retrieve a single purchase order by PurchaseOrder key from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `purchaseOrder` | string | Yes | PurchaseOrder key \(string, up to 10 characters\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_PurchaseOrderItem"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_PurchaseOrder entity | + +### `sap_s4hana_create_purchase_order` + +Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder). PurchaseOrder is auto-assigned by SAP from the document number range; provide line items via the body parameter. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `purchaseOrderType` | string | Yes | PurchaseOrderType \(e.g., "NB" Standard PO\) | +| `companyCode` | string | Yes | CompanyCode \(4 chars, e.g., "1010"\) | +| `purchasingOrganization` | string | Yes | PurchasingOrganization \(4 chars\) | +| `purchasingGroup` | string | Yes | PurchasingGroup \(3 chars\) | +| `supplier` | string | Yes | Supplier business partner key \(up to 10 chars\) | +| `body` | json | No | Additional A_PurchaseOrder fields and to_PurchaseOrderItem deep-insert items merged into the create payload \(e.g., \{"to_PurchaseOrderItem":\[\{"PurchaseOrderItem":"10","Material":"TG11","OrderQuantity":"5","Plant":"1010","PurchaseOrderQuantityUnit":"PC","NetPriceAmount":"100.00","DocumentCurrency":"USD"\}\]\}\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Created A_PurchaseOrder entity | + +### `sap_s4hana_update_purchase_order` + +Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `purchaseOrder` | string | Yes | PurchaseOrder key to update \(string, up to 10 characters\) | +| `body` | json | Yes | JSON object with A_PurchaseOrder fields to update \(e.g., \{"PurchasingGroup":"002","PurchaseOrderDate":"/Date\(1735689600000\)/"\}\) | +| `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP \(204 on success\) | +| `data` | json | Null on 204 success, or updated A_PurchaseOrder entity if SAP returns one | + +### `sap_s4hana_list_supplier_invoices` + +List supplier invoices from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice) with optional OData $filter, $top, $skip, $orderby, $select, $expand. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `filter` | string | No | OData $filter expression \(e.g., "InvoicingParty eq \'17300001\'"\) | +| `top` | number | No | Maximum results to return \($top\) | +| `skip` | number | No | Number of results to skip \($skip\) | +| `orderBy` | string | No | OData $orderby expression | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \($expand\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Array of A_SupplierInvoice entities | + +### `sap_s4hana_get_supplier_invoice` + +Retrieve a single supplier invoice by composite key (SupplierInvoice + FiscalYear) from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `supplierInvoice` | string | Yes | SupplierInvoice key \(string, up to 10 characters\) | +| `fiscalYear` | string | Yes | FiscalYear \(4-character year, e.g., "2024"\) | +| `select` | string | No | Comma-separated fields to return \($select\) | +| `expand` | string | No | Comma-separated navigation properties to expand \($expand\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | A_SupplierInvoice entity | + +### `sap_s4hana_odata_query` + +Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | +| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | +| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | +| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) | +| `username` | string | No | Username for HTTP Basic auth | +| `password` | string | No | Password for HTTP Basic auth | +| `service` | string | Yes | OData service name \(e.g., "API_BUSINESS_PARTNER", "API_SALES_ORDER_SRV"\) | +| `path` | string | Yes | Path inside the service \(e.g., "/A_BusinessPartner" or "/A_BusinessPartner\(\'1000123\'\)"\) | +| `method` | string | No | HTTP method: GET \(default\), POST, PATCH, PUT, DELETE, MERGE | +| `query` | json | No | OData query parameters as JSON object or query string \(e.g., \{"$filter":"BusinessPartnerCategory eq \'1\'","$top":10\}\). $format=json is added automatically when omitted. | +| `body` | json | No | JSON request body for write operations | +| `ifMatch` | string | No | ETag value for the If-Match header \(required by SAP for PATCH/PUT/DELETE on existing entities\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by SAP | +| `data` | json | Parsed OData payload \(entity, collection, or null on 204\) | + + diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index f5ac6f926a..4c921d3753 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -153,6 +153,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SapS4HanaIcon, SESIcon, SearchIcon, SecretsManagerIcon, @@ -349,6 +350,7 @@ export const blockTypeToIconMap: Record = { rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, + sap_s4hana: SapS4HanaIcon, search: SearchIcon, secrets_manager: SecretsManagerIcon, sendgrid: SendgridIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 0adc9ac930..68adf7e1df 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -11272,6 +11272,173 @@ "integrationTypes": ["crm", "customer-support", "sales"], "tags": ["sales-engagement", "customer-support"] }, + { + "type": "sap_s4hana", + "slug": "sap-s-4hana", + "name": "SAP S/4HANA", + "description": "Read and write SAP S/4HANA Cloud business data via OData", + "longDescription": "Connect SAP S/4HANA Cloud Public Edition with per-tenant OAuth 2.0 client credentials configured in your Communication Arrangements. Read and create business partners, customers, suppliers, sales orders, deliveries (inbound/outbound), billing documents, products, stock and material documents, purchase requisitions, purchase orders, and supplier invoices, or run arbitrary OData v2 queries against any whitelisted Communication Scenario.", + "bgColor": "#0A6ED1", + "iconName": "SapS4HanaIcon", + "docsUrl": "https://docs.sim.ai/tools/sap_s4hana", + "operations": [ + { + "name": "List Business Partners", + "description": "List business partners from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Business Partner", + "description": "Retrieve a single business partner by BusinessPartner key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner)." + }, + { + "name": "Create Business Partner", + "description": "Create a business partner in SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner). For Person category 1 provide FirstName and LastName. For Organization category 2 provide OrganizationBPName1." + }, + { + "name": "Update Business Partner", + "description": "Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + }, + { + "name": "List Customers", + "description": "List customers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Customer", + "description": "Retrieve a single customer by Customer key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer)." + }, + { + "name": "Update Customer", + "description": "" + }, + { + "name": "List Suppliers", + "description": "List suppliers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Supplier", + "description": "Retrieve a single supplier by Supplier key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier)." + }, + { + "name": "Update Supplier", + "description": "" + }, + { + "name": "List Sales Orders", + "description": "List sales orders from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Sales Order", + "description": "Retrieve a single sales order by SalesOrder key from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder)." + }, + { + "name": "Create Sales Order", + "description": "Create a sales order in SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with deep insert of sales order items via to_Item." + }, + { + "name": "Update Sales Order", + "description": "Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + }, + { + "name": "Delete Sales Order", + "description": "Delete an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Only orders without subsequent documents (deliveries, invoices) can be deleted; otherwise reject items via update instead." + }, + { + "name": "List Outbound Deliveries", + "description": "List outbound deliveries from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Outbound Delivery", + "description": "Retrieve a single outbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader)." + }, + { + "name": "List Inbound Deliveries", + "description": "List inbound deliveries from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Inbound Delivery", + "description": "Retrieve a single inbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader)." + }, + { + "name": "List Billing Documents", + "description": "List billing documents (customer invoices) from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Billing Document", + "description": "Retrieve a single billing document (customer invoice) by BillingDocument key from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument)." + }, + { + "name": "List Products", + "description": "List products (materials) from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Product", + "description": "Retrieve a single product (material) by Product key from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product)." + }, + { + "name": "Update Product", + "description": "Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PATCH only sends the fields you provide; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV PATCH/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET." + }, + { + "name": "List Material Stock", + "description": "List material stock quantities from SAP S/4HANA Cloud (API_MATERIAL_STOCK_SRV, A_MatlStkInAcctMod). The entity uses an 11-field composite key (Material, Plant, StorageLocation, Batch, Supplier, Customer, WBSElementInternalID, SDDocument, SDDocumentItem, InventorySpecialStockType, InventoryStockType) — query with $filter on these fields instead of a direct key lookup." + }, + { + "name": "List Material Documents", + "description": "List material document headers (goods movements) from SAP S/4HANA Cloud (API_MATERIAL_DOCUMENT_SRV, A_MaterialDocumentHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "List Purchase Requisitions", + "description": "List purchase requisitions from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled." + }, + { + "name": "Get Purchase Requisition", + "description": "Retrieve a single purchase requisition by PurchaseRequisition key from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled." + }, + { + "name": "Create Purchase Requisition", + "description": "Create a purchase requisition in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). PurchaseRequisition is auto-assigned by SAP from the document number range; provide line items via the to_PurchaseReqnItem deep-insert array. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled." + }, + { + "name": "Update Purchase Requisition", + "description": "" + }, + { + "name": "List Purchase Orders", + "description": "List purchase orders from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Purchase Order", + "description": "Retrieve a single purchase order by PurchaseOrder key from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder)." + }, + { + "name": "Create Purchase Order", + "description": "Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder). PurchaseOrder is auto-assigned by SAP from the document number range; provide line items via the body parameter." + }, + { + "name": "Update Purchase Order", + "description": "Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + }, + { + "name": "List Supplier Invoices", + "description": "List supplier invoices from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice) with optional OData $filter, $top, $skip, $orderby, $select, $expand." + }, + { + "name": "Get Supplier Invoice", + "description": "Retrieve a single supplier invoice by composite key (SupplierInvoice + FiscalYear) from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice)." + }, + { + "name": "OData Query (advanced)", + "description": "Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping." + } + ], + "operationCount": 37, + "triggers": [], + "triggerCount": 0, + "authType": "none", + "category": "tools", + "integrationTypes": ["other", "developer-tools"], + "tags": ["automation"] + }, { "type": "search", "slug": "search", diff --git a/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts b/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts new file mode 100644 index 0000000000..3f92c6a42e --- /dev/null +++ b/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts @@ -0,0 +1,567 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('SapS4HanaProxyAPI') + +const HttpMethod = z.enum(['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'MERGE']) +const DeploymentType = z.enum(['cloud_public', 'cloud_private', 'on_premise']) +const AuthType = z.enum(['oauth_client_credentials', 'basic']) + +const ServiceName = z + .string() + .min(1, 'service is required') + .regex( + /^[A-Z][A-Z0-9_]*(;v=\d+)?$/, + 'service must be an uppercase OData service name optionally suffixed with ";v=NNNN" (e.g., API_BUSINESS_PARTNER, API_OUTBOUND_DELIVERY_SRV;v=0002)' + ) + +const ServicePath = z + .string() + .min(1, 'path is required') + .refine((p) => !p.split(/[/\\]/).some((seg) => seg === '..' || seg === '.'), { + message: 'path must not contain ".." or "." segments', + }) + +const Subdomain = z + .string() + .regex( + /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i, + 'subdomain must contain only letters, digits, and hyphens (1-63 chars)' + ) + +const ProxyRequestSchema = z + .object({ + deploymentType: DeploymentType.default('cloud_public'), + authType: AuthType.default('oauth_client_credentials'), + subdomain: Subdomain.optional(), + region: z + .string() + .regex(/^[a-z]{2,4}\d{1,3}$/i, 'region must be an SAP BTP region code (e.g., eu10, us30)') + .optional(), + baseUrl: z.string().optional(), + tokenUrl: z.string().optional(), + clientId: z.string().optional(), + clientSecret: z.string().optional(), + username: z.string().optional(), + password: z.string().optional(), + service: ServiceName, + path: ServicePath, + method: HttpMethod.default('GET'), + query: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(), + body: z.unknown().optional(), + ifMatch: z.string().optional(), + }) + .superRefine((req, ctx) => { + if (req.deploymentType === 'cloud_public') { + if (!req.subdomain) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['subdomain'], + message: 'subdomain is required for cloud_public deployment', + }) + } + if (!req.region) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['region'], + message: 'region is required for cloud_public deployment', + }) + } + if (req.authType !== 'oauth_client_credentials') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['authType'], + message: 'cloud_public deployment only supports oauth_client_credentials', + }) + } + if (!req.clientId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['clientId'], + message: 'clientId is required', + }) + } + if (!req.clientSecret) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['clientSecret'], + message: 'clientSecret is required', + }) + } + } else { + if (!req.baseUrl) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['baseUrl'], + message: 'baseUrl is required for cloud_private and on_premise deployments', + }) + } else { + const baseUrlCheck = checkExternalUrlSafety(req.baseUrl, 'baseUrl') + if (!baseUrlCheck.ok) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['baseUrl'], + message: baseUrlCheck.message, + }) + } + } + if (req.authType === 'oauth_client_credentials') { + if (!req.tokenUrl) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['tokenUrl'], + message: 'tokenUrl is required for OAuth on cloud_private/on_premise', + }) + } else { + const tokenUrlCheck = checkExternalUrlSafety(req.tokenUrl, 'tokenUrl') + if (!tokenUrlCheck.ok) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['tokenUrl'], + message: tokenUrlCheck.message, + }) + } + } + if (!req.clientId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['clientId'], + message: 'clientId is required for OAuth', + }) + } + if (!req.clientSecret) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['clientSecret'], + message: 'clientSecret is required for OAuth', + }) + } + } else { + if (!req.username) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['username'], + message: 'username is required for Basic auth', + }) + } + if (!req.password) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['password'], + message: 'password is required for Basic auth', + }) + } + } + } + }) + +type ProxyRequest = z.infer + +interface CachedToken { + accessToken: string + expiresAt: number +} + +const TOKEN_CACHE = new Map() +const TOKEN_CACHE_MAX_ENTRIES = 500 +const TOKEN_SAFETY_WINDOW_MS = 60_000 +const OUTBOUND_FETCH_TIMEOUT_MS = 30_000 + +const FORBIDDEN_HOSTS = new Set([ + 'localhost', + '0.0.0.0', + '127.0.0.1', + '169.254.169.254', + 'metadata.google.internal', + 'metadata', + '[::1]', + '[::]', + '[::ffff:127.0.0.1]', + '[fd00:ec2::254]', +]) + +function isPrivateIPv4(host: string): boolean { + const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) + if (!match) return false + const octets = match.slice(1, 5).map(Number) as [number, number, number, number] + if (octets.some((o) => o < 0 || o > 255)) return false + const [a, b] = octets + if (a === 10) return true + if (a === 172 && b >= 16 && b <= 31) return true + if (a === 192 && b === 168) return true + if (a === 127) return true + if (a === 169 && b === 254) return true + if (a === 0) return true + return false +} + +function extractIPv4MappedHost(host: string): string | null { + const stripped = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host + const lower = stripped.toLowerCase() + const prefixes = ['::ffff:', '::'] + for (const prefix of prefixes) { + if (lower.startsWith(prefix)) { + const candidate = lower.slice(prefix.length) + if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(candidate)) return candidate + } + } + return null +} + +function checkExternalUrlSafety( + rawUrl: string, + label: string +): { ok: true; url: URL } | { ok: false; message: string } { + let parsed: URL + try { + parsed = new URL(rawUrl) + } catch { + return { ok: false, message: `${label} must be a valid URL` } + } + if (parsed.protocol !== 'https:') { + return { ok: false, message: `${label} must use https://` } + } + const host = parsed.hostname.toLowerCase() + if (FORBIDDEN_HOSTS.has(host) || FORBIDDEN_HOSTS.has(`[${host}]`)) { + return { ok: false, message: `${label} host is not allowed` } + } + if (isPrivateIPv4(host)) { + return { ok: false, message: `${label} host is not allowed (private/loopback range)` } + } + const mapped = extractIPv4MappedHost(host) + if (mapped && isPrivateIPv4(mapped)) { + return { ok: false, message: `${label} host is not allowed (IPv4-mapped private range)` } + } + return { ok: true, url: parsed } +} + +function assertSafeExternalUrl(rawUrl: string, label: string): URL { + const result = checkExternalUrlSafety(rawUrl, label) + if (!result.ok) throw new Error(result.message) + return result.url +} + +function resolveTokenUrl(req: ProxyRequest): string { + if (req.tokenUrl) return req.tokenUrl + return `https://${req.subdomain}.authentication.${req.region}.hana.ondemand.com/oauth/token` +} + +function tokenCacheKey(req: ProxyRequest): string { + return `${resolveTokenUrl(req)}::${req.clientId ?? ''}` +} + +function rememberToken(key: string, token: CachedToken): void { + if (TOKEN_CACHE.has(key)) TOKEN_CACHE.delete(key) + TOKEN_CACHE.set(key, token) + while (TOKEN_CACHE.size > TOKEN_CACHE_MAX_ENTRIES) { + const oldestKey = TOKEN_CACHE.keys().next().value + if (oldestKey === undefined) break + TOKEN_CACHE.delete(oldestKey) + } +} + +async function fetchAccessToken(req: ProxyRequest, requestId: string): Promise { + const cacheKey = tokenCacheKey(req) + const cached = TOKEN_CACHE.get(cacheKey) + if (cached && cached.expiresAt - TOKEN_SAFETY_WINDOW_MS > Date.now()) { + return cached.accessToken + } + + const tokenUrl = assertSafeExternalUrl(resolveTokenUrl(req), 'tokenUrl').toString() + const basic = Buffer.from(`${req.clientId}:${req.clientSecret}`).toString('base64') + + const response = await fetch(tokenUrl, { + method: 'POST', + headers: { + Authorization: `Basic ${basic}`, + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: 'grant_type=client_credentials', + signal: AbortSignal.timeout(OUTBOUND_FETCH_TIMEOUT_MS), + }) + + if (!response.ok) { + const text = await response.text().catch(() => '') + logger.warn(`[${requestId}] Token fetch failed (${response.status}): ${text}`) + throw new Error(`SAP token request failed: HTTP ${response.status}`) + } + + const data = (await response.json()) as { + access_token?: string + expires_in?: number + } + + if (!data.access_token) { + throw new Error('SAP token response missing access_token') + } + + const expiresInMs = (data.expires_in ?? 3600) * 1000 + rememberToken(cacheKey, { + accessToken: data.access_token, + expiresAt: Date.now() + expiresInMs, + }) + return data.access_token +} + +interface CsrfBundle { + token: string + cookie: string +} + +function joinSetCookies(headers: Headers): string { + const cookies = + typeof (headers as { getSetCookie?: () => string[] }).getSetCookie === 'function' + ? (headers as { getSetCookie: () => string[] }).getSetCookie() + : (headers.get('set-cookie') ?? '').split(/,(?=[^ ;]+=)/) + return cookies + .map((c) => c.split(';')[0]?.trim()) + .filter(Boolean) + .join('; ') +} + +function buildAuthHeader(req: ProxyRequest, accessToken: string | null): string { + if (req.authType === 'basic') { + const basic = Buffer.from(`${req.username}:${req.password}`).toString('base64') + return `Basic ${basic}` + } + return `Bearer ${accessToken}` +} + +async function fetchCsrf( + req: ProxyRequest, + accessToken: string | null, + requestId: string +): Promise { + const url = buildOdataUrl(req, '/$metadata') + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: buildAuthHeader(req, accessToken), + Accept: 'application/xml', + 'X-CSRF-Token': 'Fetch', + }, + signal: AbortSignal.timeout(OUTBOUND_FETCH_TIMEOUT_MS), + }) + + if (!response.ok) { + const text = await response.text().catch(() => '') + logger.warn(`[${requestId}] CSRF fetch failed (${response.status}): ${text}`) + return null + } + + const token = response.headers.get('x-csrf-token') + const cookie = joinSetCookies(response.headers) + if (!token) return null + return { token, cookie } +} + +function resolveHost(req: ProxyRequest): string { + if (req.baseUrl) { + const trimmed = req.baseUrl.replace(/\/+$/, '') + return assertSafeExternalUrl(trimmed, 'baseUrl').toString().replace(/\/+$/, '') + } + const constructed = `https://${req.subdomain}-api.s4hana.ondemand.com` + return assertSafeExternalUrl(constructed, 'subdomain').toString().replace(/\/+$/, '') +} + +function buildOdataUrl(req: ProxyRequest, pathOverride?: string): string { + const host = resolveHost(req) + const servicePath = `/sap/opu/odata/sap/${req.service}` + const subPath = pathOverride ?? req.path + const normalized = subPath.startsWith('/') ? subPath : `/${subPath}` + const base = `${host}${servicePath}${normalized}` + + if (!req.query || Object.keys(req.query).length === 0) { + return base + } + const search = new URLSearchParams() + for (const [key, value] of Object.entries(req.query)) { + if (value === undefined || value === null) continue + search.append(key, String(value)) + } + const queryString = search.toString() + if (!queryString) return base + return base.includes('?') ? `${base}&${queryString}` : `${base}?${queryString}` +} + +const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE', 'MERGE']) + +interface OdataInvocation { + status: number + body: unknown + raw: string + csrfHeader: string +} + +async function callOdata( + req: ProxyRequest, + accessToken: string | null, + csrf: CsrfBundle | null +): Promise { + const url = buildOdataUrl(req) + const headers: Record = { + Authorization: buildAuthHeader(req, accessToken), + Accept: 'application/json', + } + + const isWrite = WRITE_METHODS.has(req.method) + const hasBody = req.body !== undefined && req.body !== null + if (hasBody) headers['Content-Type'] = 'application/json' + if (req.ifMatch) headers['If-Match'] = req.ifMatch + + if (isWrite && csrf) { + headers['X-CSRF-Token'] = csrf.token + if (csrf.cookie) headers.Cookie = csrf.cookie + } + + const response = await fetch(url, { + method: req.method, + headers, + body: hasBody ? JSON.stringify(req.body) : undefined, + signal: AbortSignal.timeout(OUTBOUND_FETCH_TIMEOUT_MS), + }) + + const raw = await response.text() + let parsed: unknown = null + if (raw.length > 0) { + try { + parsed = JSON.parse(raw) + } catch { + parsed = raw + } + } + + const csrfHeader = response.headers.get('x-csrf-token')?.toLowerCase() ?? '' + return { status: response.status, body: parsed, raw, csrfHeader } +} + +function isCsrfRequired(invocation: OdataInvocation): boolean { + if (invocation.status !== 403) return false + if (invocation.csrfHeader === 'required') return true + if (typeof invocation.body !== 'object' || invocation.body === null) return false + const errorObj = (invocation.body as { error?: { message?: { value?: string } | string } }).error + const messageField = errorObj?.message + const message = typeof messageField === 'string' ? messageField : (messageField?.value ?? '') + return message.toLowerCase().includes('csrf') +} + +function extractOdataError(body: unknown, status: number): string { + if (body && typeof body === 'object') { + const err = ( + body as { + error?: { + message?: { value?: string } | string + code?: string + innererror?: { + errordetails?: Array<{ code?: string; message?: string; severity?: string }> + } + } + } + ).error + if (err) { + const messageField = err.message + const base = + typeof messageField === 'string' ? messageField : (messageField?.value ?? err.code ?? '') + const prefix = err.code ? `[${err.code}] ` : '' + const details = err.innererror?.errordetails + ?.filter((d) => d.message && (!d.severity || d.severity.toLowerCase() !== 'info')) + .map((d) => { + const tag = d.code ? `[${d.code}] ` : '' + return `${tag}${d.message}` + }) + .filter((m): m is string => Boolean(m)) + if (details && details.length > 0) { + const extras = details.filter((d) => !d.endsWith(base)) + return extras.length > 0 ? `${prefix}${base} (${extras.join('; ')})` : `${prefix}${base}` + } + if (base) return `${prefix}${base}` + } + } + if (typeof body === 'string' && body.length > 0) return body + return `SAP request failed with HTTP ${status}` +} + +function unwrapOdata(body: unknown): unknown { + if (!body || typeof body !== 'object') return body + const root = (body as { d?: unknown }).d + if (root === undefined) return body + if (root && typeof root === 'object' && 'results' in (root as Record)) { + const rootObj = root as { results: unknown; __count?: string; __next?: string } + if (rootObj.__count !== undefined || rootObj.__next !== undefined) { + return { + results: rootObj.results, + ...(rootObj.__count !== undefined && { __count: rootObj.__count }), + ...(rootObj.__next !== undefined && { __next: rootObj.__next }), + } + } + return rootObj.results + } + return root +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized SAP proxy request: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + const json = await request.json() + const proxyReq = ProxyRequestSchema.parse(json) + const isWrite = WRITE_METHODS.has(proxyReq.method) + + const accessToken = + proxyReq.authType === 'oauth_client_credentials' + ? await fetchAccessToken(proxyReq, requestId) + : null + const csrf = isWrite ? await fetchCsrf(proxyReq, accessToken, requestId) : null + + let invocation = await callOdata(proxyReq, accessToken, csrf) + + if (isWrite && isCsrfRequired(invocation)) { + logger.info(`[${requestId}] CSRF token rejected, refetching and retrying`) + const refreshed = await fetchCsrf(proxyReq, accessToken, requestId) + if (refreshed) { + invocation = await callOdata(proxyReq, accessToken, refreshed) + } + } + + if (invocation.status >= 200 && invocation.status < 300) { + const data = invocation.status === 204 ? null : unwrapOdata(invocation.body) + return NextResponse.json({ success: true, output: { status: invocation.status, data } }) + } + + const message = extractOdataError(invocation.body, invocation.status) + logger.warn( + `[${requestId}] SAP API error (${invocation.status}) ${proxyReq.service}${proxyReq.path}: ${message}` + ) + return NextResponse.json( + { success: false, error: message, status: invocation.status }, + { status: invocation.status } + ) + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Validation error:`, error.errors) + return NextResponse.json( + { success: false, error: error.errors[0]?.message || 'Validation failed' }, + { status: 400 } + ) + } + logger.error(`[${requestId}] Unexpected SAP proxy error:`, error) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) + } +}) diff --git a/apps/sim/blocks/blocks/sap_s4hana.ts b/apps/sim/blocks/blocks/sap_s4hana.ts new file mode 100644 index 0000000000..ec0e878024 --- /dev/null +++ b/apps/sim/blocks/blocks/sap_s4hana.ts @@ -0,0 +1,1144 @@ +import { SapS4HanaIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { SapProxyResponse } from '@/tools/sap_s4hana/types' + +export const SapS4HanaBlock: BlockConfig = { + type: 'sap_s4hana', + name: 'SAP S/4HANA', + description: 'Read and write SAP S/4HANA Cloud business data via OData', + authMode: AuthMode.ApiKey, + longDescription: + 'Connect SAP S/4HANA Cloud Public Edition with per-tenant OAuth 2.0 client credentials configured in your Communication Arrangements. Read and create business partners, customers, suppliers, sales orders, deliveries (inbound/outbound), billing documents, products, stock and material documents, purchase requisitions, purchase orders, and supplier invoices, or run arbitrary OData v2 queries against any whitelisted Communication Scenario.', + docsLink: 'https://docs.sim.ai/tools/sap_s4hana', + category: 'tools', + integrationType: IntegrationType.Other, + tags: ['automation'], + bgColor: '#0A6ED1', + icon: SapS4HanaIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Business Partners', id: 'sap_s4hana_list_business_partners' }, + { label: 'Get Business Partner', id: 'sap_s4hana_get_business_partner' }, + { label: 'Create Business Partner', id: 'sap_s4hana_create_business_partner' }, + { label: 'Update Business Partner', id: 'sap_s4hana_update_business_partner' }, + { label: 'List Customers', id: 'sap_s4hana_list_customers' }, + { label: 'Get Customer', id: 'sap_s4hana_get_customer' }, + { label: 'Update Customer', id: 'sap_s4hana_update_customer' }, + { label: 'List Suppliers', id: 'sap_s4hana_list_suppliers' }, + { label: 'Get Supplier', id: 'sap_s4hana_get_supplier' }, + { label: 'Update Supplier', id: 'sap_s4hana_update_supplier' }, + { label: 'List Sales Orders', id: 'sap_s4hana_list_sales_orders' }, + { label: 'Get Sales Order', id: 'sap_s4hana_get_sales_order' }, + { label: 'Create Sales Order', id: 'sap_s4hana_create_sales_order' }, + { label: 'Update Sales Order', id: 'sap_s4hana_update_sales_order' }, + { label: 'Delete Sales Order', id: 'sap_s4hana_delete_sales_order' }, + { label: 'List Outbound Deliveries', id: 'sap_s4hana_list_outbound_deliveries' }, + { label: 'Get Outbound Delivery', id: 'sap_s4hana_get_outbound_delivery' }, + { label: 'List Inbound Deliveries', id: 'sap_s4hana_list_inbound_deliveries' }, + { label: 'Get Inbound Delivery', id: 'sap_s4hana_get_inbound_delivery' }, + { label: 'List Billing Documents', id: 'sap_s4hana_list_billing_documents' }, + { label: 'Get Billing Document', id: 'sap_s4hana_get_billing_document' }, + { label: 'List Products', id: 'sap_s4hana_list_products' }, + { label: 'Get Product', id: 'sap_s4hana_get_product' }, + { label: 'Update Product', id: 'sap_s4hana_update_product' }, + { label: 'List Material Stock', id: 'sap_s4hana_list_material_stock' }, + { label: 'List Material Documents', id: 'sap_s4hana_list_material_documents' }, + { label: 'List Purchase Requisitions', id: 'sap_s4hana_list_purchase_requisitions' }, + { label: 'Get Purchase Requisition', id: 'sap_s4hana_get_purchase_requisition' }, + { label: 'Create Purchase Requisition', id: 'sap_s4hana_create_purchase_requisition' }, + { label: 'Update Purchase Requisition', id: 'sap_s4hana_update_purchase_requisition' }, + { label: 'List Purchase Orders', id: 'sap_s4hana_list_purchase_orders' }, + { label: 'Get Purchase Order', id: 'sap_s4hana_get_purchase_order' }, + { label: 'Create Purchase Order', id: 'sap_s4hana_create_purchase_order' }, + { label: 'Update Purchase Order', id: 'sap_s4hana_update_purchase_order' }, + { label: 'List Supplier Invoices', id: 'sap_s4hana_list_supplier_invoices' }, + { label: 'Get Supplier Invoice', id: 'sap_s4hana_get_supplier_invoice' }, + { label: 'OData Query (advanced)', id: 'sap_s4hana_odata_query' }, + ], + value: () => 'sap_s4hana_list_business_partners', + required: true, + }, + + // List filters (shared across list operations) + { + id: 'filter', + title: '$filter', + type: 'long-input', + placeholder: "BusinessPartnerCategory eq '1'", + condition: { + field: 'operation', + value: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_list_customers', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_list_products', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_list_supplier_invoices', + ], + }, + }, + { + id: 'top', + title: '$top', + type: 'short-input', + placeholder: '50', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_list_customers', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_list_products', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_list_supplier_invoices', + ], + }, + }, + { + id: 'skip', + title: '$skip', + type: 'short-input', + placeholder: '0', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_list_customers', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_list_products', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_list_supplier_invoices', + ], + }, + mode: 'advanced', + }, + { + id: 'orderBy', + title: '$orderby', + type: 'short-input', + placeholder: 'CreationDate desc', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_list_customers', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_list_products', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_list_supplier_invoices', + ], + }, + mode: 'advanced', + }, + { + id: 'select', + title: '$select', + type: 'short-input', + placeholder: 'BusinessPartner,FirstName,LastName', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_get_business_partner', + 'sap_s4hana_list_customers', + 'sap_s4hana_get_customer', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_get_supplier', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_get_sales_order', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_get_outbound_delivery', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_get_inbound_delivery', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_get_billing_document', + 'sap_s4hana_list_products', + 'sap_s4hana_get_product', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_get_purchase_requisition', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_get_purchase_order', + 'sap_s4hana_list_supplier_invoices', + 'sap_s4hana_get_supplier_invoice', + ], + }, + mode: 'advanced', + }, + { + id: 'expand', + title: '$expand', + type: 'short-input', + placeholder: 'to_Item', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_get_business_partner', + 'sap_s4hana_list_customers', + 'sap_s4hana_get_customer', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_get_supplier', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_get_sales_order', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_get_outbound_delivery', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_get_inbound_delivery', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_get_billing_document', + 'sap_s4hana_list_products', + 'sap_s4hana_get_product', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_get_purchase_requisition', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_get_purchase_order', + 'sap_s4hana_list_supplier_invoices', + 'sap_s4hana_get_supplier_invoice', + ], + }, + mode: 'advanced', + }, + + // Business Partner: get/create + { + id: 'businessPartner', + title: 'BusinessPartner', + type: 'short-input', + placeholder: '1000123', + condition: { + field: 'operation', + value: ['sap_s4hana_get_business_partner', 'sap_s4hana_update_business_partner'], + }, + required: true, + }, + { + id: 'businessPartnerCategory', + title: 'BusinessPartnerCategory', + type: 'dropdown', + options: [ + { label: '1 — Person', id: '1' }, + { label: '2 — Organization', id: '2' }, + { label: '3 — Group', id: '3' }, + ], + value: () => '2', + condition: { field: 'operation', value: 'sap_s4hana_create_business_partner' }, + required: true, + }, + { + id: 'businessPartnerGrouping', + title: 'BusinessPartnerGrouping', + type: 'short-input', + placeholder: 'Tenant-configured grouping (see customizing)', + condition: { field: 'operation', value: 'sap_s4hana_create_business_partner' }, + required: true, + }, + { + id: 'firstName', + title: 'FirstName', + type: 'short-input', + placeholder: 'Required for Person', + condition: { + field: 'operation', + value: 'sap_s4hana_create_business_partner', + and: { field: 'businessPartnerCategory', value: '1' }, + }, + required: { + field: 'operation', + value: 'sap_s4hana_create_business_partner', + and: { field: 'businessPartnerCategory', value: '1' }, + }, + }, + { + id: 'lastName', + title: 'LastName', + type: 'short-input', + placeholder: 'Required for Person', + condition: { + field: 'operation', + value: 'sap_s4hana_create_business_partner', + and: { field: 'businessPartnerCategory', value: '1' }, + }, + required: { + field: 'operation', + value: 'sap_s4hana_create_business_partner', + and: { field: 'businessPartnerCategory', value: '1' }, + }, + }, + { + id: 'organizationBPName1', + title: 'OrganizationBPName1', + type: 'short-input', + placeholder: 'Required for Organization', + condition: { + field: 'operation', + value: 'sap_s4hana_create_business_partner', + and: { field: 'businessPartnerCategory', value: '2' }, + }, + required: { + field: 'operation', + value: 'sap_s4hana_create_business_partner', + and: { field: 'businessPartnerCategory', value: '2' }, + }, + }, + { + id: 'businessPartnerBody', + title: 'Additional Fields (JSON)', + type: 'code', + placeholder: '{"CorrespondenceLanguage":"EN"}', + condition: { field: 'operation', value: 'sap_s4hana_create_business_partner' }, + mode: 'advanced', + }, + + // Customer: get + { + id: 'customer', + title: 'Customer', + type: 'short-input', + placeholder: '17100001', + condition: { + field: 'operation', + value: ['sap_s4hana_get_customer', 'sap_s4hana_update_customer'], + }, + required: true, + }, + + // Sales Order: get/create + { + id: 'salesOrder', + title: 'SalesOrder', + type: 'short-input', + placeholder: '1', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_get_sales_order', + 'sap_s4hana_update_sales_order', + 'sap_s4hana_delete_sales_order', + ], + }, + required: true, + }, + { + id: 'salesOrderType', + title: 'SalesOrderType', + type: 'short-input', + placeholder: 'OR', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + required: true, + }, + { + id: 'salesOrganization', + title: 'SalesOrganization', + type: 'short-input', + placeholder: '1010', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + required: true, + }, + { + id: 'distributionChannel', + title: 'DistributionChannel', + type: 'short-input', + placeholder: '10', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + required: true, + }, + { + id: 'organizationDivision', + title: 'OrganizationDivision', + type: 'short-input', + placeholder: '00', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + required: true, + }, + { + id: 'soldToParty', + title: 'SoldToParty', + type: 'short-input', + placeholder: '17100001', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + required: true, + }, + { + id: 'salesOrderItems', + title: 'Items (to_Item, JSON array)', + type: 'code', + placeholder: '[{"Material":"TG11","RequestedQuantity":"1"}]', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + required: true, + }, + { + id: 'salesOrderBody', + title: 'Additional Fields (JSON)', + type: 'code', + placeholder: '{"PurchaseOrderByCustomer":"PO-12345"}', + condition: { field: 'operation', value: 'sap_s4hana_create_sales_order' }, + mode: 'advanced', + }, + + // Delivery Document: shared by outbound and inbound + { + id: 'deliveryDocument', + title: 'DeliveryDocument', + type: 'short-input', + placeholder: '80000000', + condition: { + field: 'operation', + value: ['sap_s4hana_get_outbound_delivery', 'sap_s4hana_get_inbound_delivery'], + }, + required: true, + }, + + // Billing Document: get + { + id: 'billingDocument', + title: 'BillingDocument', + type: 'short-input', + placeholder: '90000000', + condition: { field: 'operation', value: 'sap_s4hana_get_billing_document' }, + required: true, + }, + + // Product: get + { + id: 'product', + title: 'Product', + type: 'short-input', + placeholder: 'TG11', + condition: { + field: 'operation', + value: ['sap_s4hana_get_product', 'sap_s4hana_update_product'], + }, + required: true, + }, + + // Purchase Requisition: get/update + { + id: 'purchaseRequisition', + title: 'PurchaseRequisition', + type: 'short-input', + placeholder: '10000000', + condition: { + field: 'operation', + value: ['sap_s4hana_get_purchase_requisition', 'sap_s4hana_update_purchase_requisition'], + }, + required: true, + }, + // Purchase Requisition: create + { + id: 'purchaseRequisitionType', + title: 'PurchaseRequisitionType', + type: 'short-input', + placeholder: 'NB', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_requisition' }, + required: true, + }, + { + id: 'purchaseRequisitionItems', + title: 'Items (to_PurchaseReqnItem, JSON array)', + type: 'code', + placeholder: + '[{"PurchaseRequisitionItem":"10","Material":"TG11","RequestedQuantity":"5","Plant":"1010","BaseUnit":"PC"}]', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_requisition' }, + required: true, + }, + { + id: 'purchaseRequisitionBody', + title: 'Additional Fields (JSON)', + type: 'code', + placeholder: '{"PurchaseRequisitionDescription":"Office supplies"}', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_requisition' }, + mode: 'advanced', + }, + + // Purchase Order: get/create + { + id: 'purchaseOrder', + title: 'PurchaseOrder', + type: 'short-input', + placeholder: '4500000001', + condition: { + field: 'operation', + value: ['sap_s4hana_get_purchase_order', 'sap_s4hana_update_purchase_order'], + }, + required: true, + }, + { + id: 'purchaseOrderType', + title: 'PurchaseOrderType', + type: 'short-input', + placeholder: 'NB', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_order' }, + required: true, + }, + { + id: 'companyCode', + title: 'CompanyCode', + type: 'short-input', + placeholder: '1010', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_order' }, + required: true, + }, + { + id: 'purchasingOrganization', + title: 'PurchasingOrganization', + type: 'short-input', + placeholder: '1010', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_order' }, + required: true, + }, + { + id: 'purchasingGroup', + title: 'PurchasingGroup', + type: 'short-input', + placeholder: '001', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_order' }, + required: true, + }, + { + id: 'supplier', + title: 'Supplier', + type: 'short-input', + placeholder: '17300001', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_create_purchase_order', + 'sap_s4hana_get_supplier', + 'sap_s4hana_update_supplier', + ], + }, + required: true, + }, + { + id: 'purchaseOrderBody', + title: 'Items & Additional Fields (JSON)', + type: 'code', + placeholder: + '{"to_PurchaseOrderItem":[{"PurchaseOrderItem":"10","Material":"TG11","OrderQuantity":"5","Plant":"1010","PurchaseOrderQuantityUnit":"PC","NetPriceAmount":"100.00","DocumentCurrency":"USD"}]}', + condition: { field: 'operation', value: 'sap_s4hana_create_purchase_order' }, + required: true, + }, + + // Supplier Invoice: get + { + id: 'supplierInvoice', + title: 'SupplierInvoice', + type: 'short-input', + placeholder: '5105600000', + condition: { field: 'operation', value: 'sap_s4hana_get_supplier_invoice' }, + required: true, + }, + { + id: 'fiscalYear', + title: 'FiscalYear', + type: 'short-input', + placeholder: '2024', + condition: { field: 'operation', value: 'sap_s4hana_get_supplier_invoice' }, + required: true, + }, + + // Shared body for all PATCH update operations + { + id: 'updateBody', + title: 'Fields to Update (JSON)', + type: 'code', + placeholder: '{"FirstName":"Jane","SearchTerm1":"VIP"}', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_update_business_partner', + 'sap_s4hana_update_customer', + 'sap_s4hana_update_supplier', + 'sap_s4hana_update_product', + 'sap_s4hana_update_sales_order', + 'sap_s4hana_update_purchase_order', + 'sap_s4hana_update_purchase_requisition', + ], + }, + required: true, + }, + // Shared If-Match for all update + delete operations + { + id: 'updateIfMatch', + title: 'If-Match (ETag)', + type: 'short-input', + placeholder: '* (default — bypass concurrency check)', + condition: { + field: 'operation', + value: [ + 'sap_s4hana_update_business_partner', + 'sap_s4hana_update_customer', + 'sap_s4hana_update_supplier', + 'sap_s4hana_update_product', + 'sap_s4hana_update_sales_order', + 'sap_s4hana_delete_sales_order', + 'sap_s4hana_update_purchase_order', + 'sap_s4hana_update_purchase_requisition', + ], + }, + mode: 'advanced', + }, + + // OData Query passthrough + { + id: 'odataService', + title: 'OData Service', + type: 'short-input', + placeholder: 'API_BUSINESS_PARTNER', + condition: { field: 'operation', value: 'sap_s4hana_odata_query' }, + required: true, + }, + { + id: 'odataPath', + title: 'Entity Path', + type: 'short-input', + placeholder: "/A_BusinessPartner('1000123')", + condition: { field: 'operation', value: 'sap_s4hana_odata_query' }, + required: true, + }, + { + id: 'odataMethod', + title: 'HTTP Method', + type: 'dropdown', + options: [ + { label: 'GET', id: 'GET' }, + { label: 'POST', id: 'POST' }, + { label: 'PATCH', id: 'PATCH' }, + { label: 'PUT', id: 'PUT' }, + { label: 'DELETE', id: 'DELETE' }, + { label: 'MERGE', id: 'MERGE' }, + ], + value: () => 'GET', + condition: { field: 'operation', value: 'sap_s4hana_odata_query' }, + }, + { + id: 'odataQuery', + title: 'Query Parameters (JSON or query string)', + type: 'code', + placeholder: '{"$filter":"BusinessPartnerCategory eq \'1\'","$top":10}', + condition: { field: 'operation', value: 'sap_s4hana_odata_query' }, + mode: 'advanced', + }, + { + id: 'odataBody', + title: 'Request Body (JSON)', + type: 'code', + placeholder: '{"FirstName":"Jane"}', + condition: { field: 'operation', value: 'sap_s4hana_odata_query' }, + mode: 'advanced', + }, + { + id: 'odataIfMatch', + title: 'If-Match (ETag)', + type: 'short-input', + placeholder: 'W/"datetimeoffset\'2024-01-01T00:00:00Z\'"', + condition: { field: 'operation', value: 'sap_s4hana_odata_query' }, + mode: 'advanced', + }, + + // Connection (always shown) + { + id: 'deploymentType', + title: 'Deployment', + type: 'dropdown', + options: [ + { label: 'S/4HANA Cloud Public Edition', id: 'cloud_public' }, + { label: 'S/4HANA Cloud Private Edition (RISE)', id: 'cloud_private' }, + { label: 'S/4HANA On-Premise', id: 'on_premise' }, + ], + value: () => 'cloud_public', + required: true, + }, + { + id: 'authType', + title: 'Authentication', + type: 'dropdown', + options: [ + { label: 'OAuth 2.0 Client Credentials', id: 'oauth_client_credentials' }, + { label: 'Basic (Communication User)', id: 'basic' }, + ], + value: () => 'oauth_client_credentials', + condition: { field: 'deploymentType', value: ['cloud_private', 'on_premise'] }, + required: { field: 'deploymentType', value: ['cloud_private', 'on_premise'] }, + dependsOn: ['deploymentType'], + }, + + // Cloud Public: subdomain + region (SAP BTP UAA pattern) + { + id: 'subdomain', + title: 'BTP Subdomain', + type: 'short-input', + placeholder: 'my-tenant', + condition: { field: 'deploymentType', value: 'cloud_public' }, + required: { field: 'deploymentType', value: 'cloud_public' }, + }, + { + id: 'region', + title: 'BTP Region', + type: 'dropdown', + options: [ + { label: 'eu10 — Europe / Frankfurt (AWS)', id: 'eu10' }, + { label: 'eu11 — Europe / Frankfurt (AWS, EU Access)', id: 'eu11' }, + { label: 'eu20 — Europe / Netherlands (Azure)', id: 'eu20' }, + { label: 'eu22 — Europe / Zurich (Azure)', id: 'eu22' }, + { label: 'eu30 — Europe / Frankfurt (GCP)', id: 'eu30' }, + { label: 'uk20 — UK South (Azure)', id: 'uk20' }, + { label: 'ch20 — Switzerland North (Azure)', id: 'ch20' }, + { label: 'us10 — US East / Virginia (AWS)', id: 'us10' }, + { label: 'us11 — US West / Oregon (AWS)', id: 'us11' }, + { label: 'us20 — US East 2 / Virginia (Azure)', id: 'us20' }, + { label: 'us21 — US Central / Iowa (Azure)', id: 'us21' }, + { label: 'us30 — US Central / Iowa (GCP)', id: 'us30' }, + { label: 'ca10 — Canada / Montreal (AWS)', id: 'ca10' }, + { label: 'ca20 — Canada Central / Toronto (Azure)', id: 'ca20' }, + { label: 'br10 — Brazil / São Paulo (AWS)', id: 'br10' }, + { label: 'br20 — Brazil South (Azure)', id: 'br20' }, + { label: 'br30 — Brazil / São Paulo (GCP)', id: 'br30' }, + { label: 'jp10 — Japan / Tokyo (AWS)', id: 'jp10' }, + { label: 'jp20 — Japan East / Tokyo (Azure)', id: 'jp20' }, + { label: 'jp30 — Japan / Tokyo (GCP)', id: 'jp30' }, + { label: 'jp31 — Japan / Osaka (GCP)', id: 'jp31' }, + { label: 'ap10 — Australia / Sydney (AWS)', id: 'ap10' }, + { label: 'ap11 — Singapore (AWS)', id: 'ap11' }, + { label: 'ap12 — South Korea / Seoul (AWS)', id: 'ap12' }, + { label: 'ap20 — Australia East / Sydney (Azure)', id: 'ap20' }, + { label: 'ap21 — East Asia / Hong Kong (Azure)', id: 'ap21' }, + { label: 'ap30 — Asia Pacific / Sydney (GCP)', id: 'ap30' }, + { label: 'in30 — India (GCP)', id: 'in30' }, + { label: 'il30 — Israel (GCP)', id: 'il30' }, + { label: 'sa30 — Saudi Arabia / Dammam (GCP)', id: 'sa30' }, + { label: 'sa31 — Saudi Arabia / Riyadh (GCP)', id: 'sa31' }, + ], + value: () => 'eu10', + condition: { field: 'deploymentType', value: 'cloud_public' }, + required: { field: 'deploymentType', value: 'cloud_public' }, + }, + + // Private / On-Prem: explicit host (and token URL for OAuth) + { + id: 'baseUrl', + title: 'Base URL', + type: 'short-input', + placeholder: 'https://s4h.example.com:44300', + condition: { field: 'deploymentType', value: ['cloud_private', 'on_premise'] }, + required: { field: 'deploymentType', value: ['cloud_private', 'on_premise'] }, + }, + { + id: 'tokenUrl', + title: 'OAuth Token URL', + type: 'short-input', + placeholder: 'https://auth.example.com/oauth/token', + condition: { + field: 'deploymentType', + value: ['cloud_private', 'on_premise'], + and: { field: 'authType', value: 'oauth_client_credentials' }, + }, + required: { + field: 'deploymentType', + value: ['cloud_private', 'on_premise'], + and: { field: 'authType', value: 'oauth_client_credentials' }, + }, + }, + + // OAuth credentials (shown whenever authType is oauth_client_credentials — cloud_public defaults to this) + { + id: 'clientId', + title: 'OAuth Client ID', + type: 'short-input', + placeholder: 'sb-...!b1234', + password: true, + condition: { field: 'authType', value: 'basic', not: true }, + required: { field: 'authType', value: 'basic', not: true }, + }, + { + id: 'clientSecret', + title: 'OAuth Client Secret', + type: 'short-input', + placeholder: 'Client secret from Communication Arrangement', + password: true, + condition: { field: 'authType', value: 'basic', not: true }, + required: { field: 'authType', value: 'basic', not: true }, + }, + + // Basic credentials (only surfaced on Private/On-Prem + Basic auth) + { + id: 'username', + title: 'Username', + type: 'short-input', + placeholder: 'Communication user (e.g., CC_ORDERS_USER)', + condition: { field: 'authType', value: 'basic' }, + required: { field: 'authType', value: 'basic' }, + }, + { + id: 'password', + title: 'Password', + type: 'short-input', + placeholder: 'Password for the communication user', + password: true, + condition: { field: 'authType', value: 'basic' }, + required: { field: 'authType', value: 'basic' }, + }, + ], + tools: { + access: [ + 'sap_s4hana_list_business_partners', + 'sap_s4hana_get_business_partner', + 'sap_s4hana_create_business_partner', + 'sap_s4hana_update_business_partner', + 'sap_s4hana_list_customers', + 'sap_s4hana_get_customer', + 'sap_s4hana_update_customer', + 'sap_s4hana_list_suppliers', + 'sap_s4hana_get_supplier', + 'sap_s4hana_update_supplier', + 'sap_s4hana_list_sales_orders', + 'sap_s4hana_get_sales_order', + 'sap_s4hana_create_sales_order', + 'sap_s4hana_update_sales_order', + 'sap_s4hana_delete_sales_order', + 'sap_s4hana_list_outbound_deliveries', + 'sap_s4hana_get_outbound_delivery', + 'sap_s4hana_list_inbound_deliveries', + 'sap_s4hana_get_inbound_delivery', + 'sap_s4hana_list_billing_documents', + 'sap_s4hana_get_billing_document', + 'sap_s4hana_list_products', + 'sap_s4hana_get_product', + 'sap_s4hana_update_product', + 'sap_s4hana_list_material_stock', + 'sap_s4hana_list_material_documents', + 'sap_s4hana_list_purchase_requisitions', + 'sap_s4hana_get_purchase_requisition', + 'sap_s4hana_create_purchase_requisition', + 'sap_s4hana_update_purchase_requisition', + 'sap_s4hana_list_purchase_orders', + 'sap_s4hana_get_purchase_order', + 'sap_s4hana_create_purchase_order', + 'sap_s4hana_update_purchase_order', + 'sap_s4hana_list_supplier_invoices', + 'sap_s4hana_get_supplier_invoice', + 'sap_s4hana_odata_query', + ], + config: { + tool: (params) => params.operation, + params: (params) => { + const auth = { + deploymentType: params.deploymentType || 'cloud_public', + authType: params.authType || 'oauth_client_credentials', + subdomain: params.subdomain || undefined, + region: params.region || undefined, + baseUrl: params.baseUrl || undefined, + tokenUrl: params.tokenUrl || undefined, + clientId: params.clientId || undefined, + clientSecret: params.clientSecret || undefined, + username: params.username || undefined, + password: params.password || undefined, + } + const listFields = { + filter: params.filter || undefined, + top: params.top ? Number(params.top) : undefined, + skip: params.skip ? Number(params.skip) : undefined, + orderBy: params.orderBy || undefined, + select: params.select || undefined, + expand: params.expand || undefined, + } + const entityFields = { + select: params.select || undefined, + expand: params.expand || undefined, + } + + switch (params.operation) { + case 'sap_s4hana_list_business_partners': + return { ...auth, ...listFields } + case 'sap_s4hana_get_business_partner': + return { ...auth, ...entityFields, businessPartner: params.businessPartner } + case 'sap_s4hana_create_business_partner': + return { + ...auth, + businessPartnerCategory: params.businessPartnerCategory, + businessPartnerGrouping: params.businessPartnerGrouping, + firstName: params.firstName || undefined, + lastName: params.lastName || undefined, + organizationBPName1: params.organizationBPName1 || undefined, + body: params.businessPartnerBody || undefined, + } + case 'sap_s4hana_update_business_partner': + return { + ...auth, + businessPartner: params.businessPartner, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_customers': + return { ...auth, ...listFields } + case 'sap_s4hana_get_customer': + return { ...auth, ...entityFields, customer: params.customer } + case 'sap_s4hana_update_customer': + return { + ...auth, + customer: params.customer, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_suppliers': + return { ...auth, ...listFields } + case 'sap_s4hana_get_supplier': + return { ...auth, ...entityFields, supplier: params.supplier } + case 'sap_s4hana_update_supplier': + return { + ...auth, + supplier: params.supplier, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_sales_orders': + return { ...auth, ...listFields } + case 'sap_s4hana_get_sales_order': + return { ...auth, ...entityFields, salesOrder: params.salesOrder } + case 'sap_s4hana_create_sales_order': + return { + ...auth, + salesOrderType: params.salesOrderType, + salesOrganization: params.salesOrganization, + distributionChannel: params.distributionChannel, + organizationDivision: params.organizationDivision, + soldToParty: params.soldToParty, + items: params.salesOrderItems, + body: params.salesOrderBody || undefined, + } + case 'sap_s4hana_update_sales_order': + return { + ...auth, + salesOrder: params.salesOrder, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_delete_sales_order': + return { + ...auth, + salesOrder: params.salesOrder, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_outbound_deliveries': + return { ...auth, ...listFields } + case 'sap_s4hana_get_outbound_delivery': + return { + ...auth, + ...entityFields, + deliveryDocument: params.deliveryDocument, + } + case 'sap_s4hana_list_inbound_deliveries': + return { ...auth, ...listFields } + case 'sap_s4hana_get_inbound_delivery': + return { + ...auth, + ...entityFields, + deliveryDocument: params.deliveryDocument, + } + case 'sap_s4hana_list_billing_documents': + return { ...auth, ...listFields } + case 'sap_s4hana_get_billing_document': + return { ...auth, ...entityFields, billingDocument: params.billingDocument } + case 'sap_s4hana_list_products': + return { ...auth, ...listFields } + case 'sap_s4hana_get_product': + return { ...auth, ...entityFields, product: params.product } + case 'sap_s4hana_update_product': + return { + ...auth, + product: params.product, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_material_stock': + return { ...auth, ...listFields } + case 'sap_s4hana_list_material_documents': + return { ...auth, ...listFields } + case 'sap_s4hana_list_purchase_requisitions': + return { ...auth, ...listFields } + case 'sap_s4hana_get_purchase_requisition': + return { + ...auth, + ...entityFields, + purchaseRequisition: params.purchaseRequisition, + } + case 'sap_s4hana_create_purchase_requisition': + return { + ...auth, + purchaseRequisitionType: params.purchaseRequisitionType, + items: params.purchaseRequisitionItems, + body: params.purchaseRequisitionBody || undefined, + } + case 'sap_s4hana_update_purchase_requisition': + return { + ...auth, + purchaseRequisition: params.purchaseRequisition, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_purchase_orders': + return { ...auth, ...listFields } + case 'sap_s4hana_get_purchase_order': + return { ...auth, ...entityFields, purchaseOrder: params.purchaseOrder } + case 'sap_s4hana_create_purchase_order': + return { + ...auth, + purchaseOrderType: params.purchaseOrderType, + companyCode: params.companyCode, + purchasingOrganization: params.purchasingOrganization, + purchasingGroup: params.purchasingGroup, + supplier: params.supplier, + body: params.purchaseOrderBody || undefined, + } + case 'sap_s4hana_update_purchase_order': + return { + ...auth, + purchaseOrder: params.purchaseOrder, + body: params.updateBody, + ifMatch: params.updateIfMatch || undefined, + } + case 'sap_s4hana_list_supplier_invoices': + return { ...auth, ...listFields } + case 'sap_s4hana_get_supplier_invoice': + return { + ...auth, + ...entityFields, + supplierInvoice: params.supplierInvoice, + fiscalYear: params.fiscalYear, + } + case 'sap_s4hana_odata_query': + return { + ...auth, + service: params.odataService, + path: params.odataPath, + method: params.odataMethod || 'GET', + query: params.odataQuery || undefined, + body: params.odataBody || undefined, + ifMatch: params.odataIfMatch || undefined, + } + default: + return auth + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + deploymentType: { + type: 'string', + description: 'cloud_public | cloud_private | on_premise', + }, + authType: { + type: 'string', + description: 'oauth_client_credentials | basic', + }, + subdomain: { type: 'string', description: 'BTP subdomain (Cloud Public)' }, + region: { type: 'string', description: 'BTP region (Cloud Public, e.g., eu10, us10)' }, + baseUrl: { type: 'string', description: 'Base URL (Cloud Private / On-Premise)' }, + tokenUrl: { + type: 'string', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + clientId: { type: 'string', description: 'OAuth client ID' }, + clientSecret: { type: 'string', description: 'OAuth client secret' }, + username: { type: 'string', description: 'Username (Basic auth)' }, + password: { type: 'string', description: 'Password (Basic auth)' }, + filter: { type: 'string', description: 'OData $filter expression' }, + top: { type: 'number', description: 'OData $top' }, + skip: { type: 'number', description: 'OData $skip' }, + orderBy: { type: 'string', description: 'OData $orderby expression' }, + select: { type: 'string', description: 'OData $select fields' }, + expand: { type: 'string', description: 'OData $expand navigation properties' }, + businessPartner: { type: 'string', description: 'BusinessPartner key' }, + businessPartnerCategory: { type: 'string', description: 'BusinessPartnerCategory (1, 2, 3)' }, + businessPartnerGrouping: { type: 'string', description: 'BusinessPartnerGrouping' }, + firstName: { type: 'string', description: 'FirstName for Person' }, + lastName: { type: 'string', description: 'LastName for Person' }, + organizationBPName1: { type: 'string', description: 'OrganizationBPName1 for Organization' }, + businessPartnerBody: { type: 'json', description: 'Additional A_BusinessPartner fields' }, + customer: { type: 'string', description: 'Customer key' }, + salesOrder: { type: 'string', description: 'SalesOrder key' }, + salesOrderType: { type: 'string', description: 'SalesOrderType' }, + salesOrganization: { type: 'string', description: 'SalesOrganization' }, + distributionChannel: { type: 'string', description: 'DistributionChannel' }, + organizationDivision: { type: 'string', description: 'OrganizationDivision' }, + soldToParty: { type: 'string', description: 'SoldToParty business partner key' }, + salesOrderItems: { type: 'json', description: 'Sales order items for to_Item deep insert' }, + salesOrderBody: { type: 'json', description: 'Additional A_SalesOrder fields' }, + deliveryDocument: { type: 'string', description: 'DeliveryDocument key' }, + billingDocument: { type: 'string', description: 'BillingDocument key' }, + product: { type: 'string', description: 'Product key' }, + purchaseRequisition: { type: 'string', description: 'PurchaseRequisition key' }, + purchaseRequisitionType: { type: 'string', description: 'PurchaseRequisitionType' }, + purchaseRequisitionItems: { + type: 'json', + description: 'Purchase requisition items for to_PurchaseReqnItem deep insert', + }, + purchaseRequisitionBody: { + type: 'json', + description: 'Additional A_PurchaseRequisitionHeader fields', + }, + purchaseOrder: { type: 'string', description: 'PurchaseOrder key' }, + purchaseOrderType: { type: 'string', description: 'PurchaseOrderType' }, + companyCode: { type: 'string', description: 'CompanyCode' }, + purchasingOrganization: { type: 'string', description: 'PurchasingOrganization' }, + purchasingGroup: { type: 'string', description: 'PurchasingGroup' }, + supplier: { type: 'string', description: 'Supplier business partner key' }, + purchaseOrderBody: { type: 'json', description: 'Items and additional A_PurchaseOrder fields' }, + supplierInvoice: { type: 'string', description: 'SupplierInvoice key' }, + fiscalYear: { type: 'string', description: 'FiscalYear (4-digit year)' }, + odataService: { type: 'string', description: 'OData service name' }, + odataPath: { type: 'string', description: 'OData entity path' }, + odataMethod: { type: 'string', description: 'HTTP method for OData call' }, + odataQuery: { type: 'json', description: 'OData query parameters' }, + odataBody: { type: 'json', description: 'OData request body' }, + odataIfMatch: { type: 'string', description: 'If-Match ETag header' }, + updateBody: { type: 'json', description: 'JSON object with fields to update' }, + updateIfMatch: { + type: 'string', + description: 'If-Match ETag for update/delete (defaults to "*")', + }, + }, + outputs: { + success: { type: 'boolean', description: 'Whether the operation succeeded' }, + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Parsed OData payload (entity, collection, or null)' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index bb5eddb875..d1f783f41b 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -168,6 +168,7 @@ import { RouterBlock, RouterV2Block } from '@/blocks/blocks/router' import { RssBlock } from '@/blocks/blocks/rss' import { S3Block } from '@/blocks/blocks/s3' import { SalesforceBlock } from '@/blocks/blocks/salesforce' +import { SapS4HanaBlock } from '@/blocks/blocks/sap_s4hana' import { ScheduleBlock } from '@/blocks/blocks/schedule' import { SearchBlock } from '@/blocks/blocks/search' import { SecretsManagerBlock } from '@/blocks/blocks/secrets_manager' @@ -417,6 +418,7 @@ export const registry: Record = { rss: RssBlock, s3: S3Block, salesforce: SalesforceBlock, + sap_s4hana: SapS4HanaBlock, schedule: ScheduleBlock, search: SearchBlock, sendgrid: SendGridBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 69840638eb..d6647ee3ec 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4068,6 +4068,39 @@ export function SalesforceIcon(props: SVGProps) { ) } +export function SapS4HanaIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + ) +} + export function ServiceNowIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index b365143a00..c115c2c13f 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -2224,6 +2224,45 @@ import { salesforceUpdateOpportunityTool, salesforceUpdateTaskTool, } from '@/tools/salesforce' +import { + createBusinessPartnerTool as sapS4HanaCreateBusinessPartnerTool, + createPurchaseOrderTool as sapS4HanaCreatePurchaseOrderTool, + createPurchaseRequisitionTool as sapS4HanaCreatePurchaseRequisitionTool, + createSalesOrderTool as sapS4HanaCreateSalesOrderTool, + deleteSalesOrderTool as sapS4HanaDeleteSalesOrderTool, + getBillingDocumentTool as sapS4HanaGetBillingDocumentTool, + getBusinessPartnerTool as sapS4HanaGetBusinessPartnerTool, + getCustomerTool as sapS4HanaGetCustomerTool, + getInboundDeliveryTool as sapS4HanaGetInboundDeliveryTool, + getOutboundDeliveryTool as sapS4HanaGetOutboundDeliveryTool, + getProductTool as sapS4HanaGetProductTool, + getPurchaseOrderTool as sapS4HanaGetPurchaseOrderTool, + getPurchaseRequisitionTool as sapS4HanaGetPurchaseRequisitionTool, + getSalesOrderTool as sapS4HanaGetSalesOrderTool, + getSupplierInvoiceTool as sapS4HanaGetSupplierInvoiceTool, + getSupplierTool as sapS4HanaGetSupplierTool, + listBillingDocumentsTool as sapS4HanaListBillingDocumentsTool, + listBusinessPartnersTool as sapS4HanaListBusinessPartnersTool, + listCustomersTool as sapS4HanaListCustomersTool, + listInboundDeliveriesTool as sapS4HanaListInboundDeliveriesTool, + listMaterialDocumentsTool as sapS4HanaListMaterialDocumentsTool, + listMaterialStockTool as sapS4HanaListMaterialStockTool, + listOutboundDeliveriesTool as sapS4HanaListOutboundDeliveriesTool, + listProductsTool as sapS4HanaListProductsTool, + listPurchaseOrdersTool as sapS4HanaListPurchaseOrdersTool, + listPurchaseRequisitionsTool as sapS4HanaListPurchaseRequisitionsTool, + listSalesOrdersTool as sapS4HanaListSalesOrdersTool, + listSupplierInvoicesTool as sapS4HanaListSupplierInvoicesTool, + listSuppliersTool as sapS4HanaListSuppliersTool, + odataQueryTool as sapS4HanaOdataQueryTool, + updateBusinessPartnerTool as sapS4HanaUpdateBusinessPartnerTool, + updateCustomerTool as sapS4HanaUpdateCustomerTool, + updateProductTool as sapS4HanaUpdateProductTool, + updatePurchaseOrderTool as sapS4HanaUpdatePurchaseOrderTool, + updatePurchaseRequisitionTool as sapS4HanaUpdatePurchaseRequisitionTool, + updateSalesOrderTool as sapS4HanaUpdateSalesOrderTool, + updateSupplierTool as sapS4HanaUpdateSupplierTool, +} from '@/tools/sap_s4hana' import { searchTool } from '@/tools/search' import { secretsManagerCreateSecretTool, @@ -5232,6 +5271,43 @@ export const tools: Record = { salesforce_query_more: salesforceQueryMoreTool, salesforce_describe_object: salesforceDescribeObjectTool, salesforce_list_objects: salesforceListObjectsTool, + sap_s4hana_create_business_partner: sapS4HanaCreateBusinessPartnerTool, + sap_s4hana_create_purchase_order: sapS4HanaCreatePurchaseOrderTool, + sap_s4hana_create_purchase_requisition: sapS4HanaCreatePurchaseRequisitionTool, + sap_s4hana_create_sales_order: sapS4HanaCreateSalesOrderTool, + sap_s4hana_delete_sales_order: sapS4HanaDeleteSalesOrderTool, + sap_s4hana_get_billing_document: sapS4HanaGetBillingDocumentTool, + sap_s4hana_get_business_partner: sapS4HanaGetBusinessPartnerTool, + sap_s4hana_get_customer: sapS4HanaGetCustomerTool, + sap_s4hana_get_inbound_delivery: sapS4HanaGetInboundDeliveryTool, + sap_s4hana_get_outbound_delivery: sapS4HanaGetOutboundDeliveryTool, + sap_s4hana_get_product: sapS4HanaGetProductTool, + sap_s4hana_get_purchase_order: sapS4HanaGetPurchaseOrderTool, + sap_s4hana_get_purchase_requisition: sapS4HanaGetPurchaseRequisitionTool, + sap_s4hana_get_sales_order: sapS4HanaGetSalesOrderTool, + sap_s4hana_get_supplier: sapS4HanaGetSupplierTool, + sap_s4hana_get_supplier_invoice: sapS4HanaGetSupplierInvoiceTool, + sap_s4hana_list_billing_documents: sapS4HanaListBillingDocumentsTool, + sap_s4hana_list_business_partners: sapS4HanaListBusinessPartnersTool, + sap_s4hana_list_customers: sapS4HanaListCustomersTool, + sap_s4hana_list_inbound_deliveries: sapS4HanaListInboundDeliveriesTool, + sap_s4hana_list_material_documents: sapS4HanaListMaterialDocumentsTool, + sap_s4hana_list_material_stock: sapS4HanaListMaterialStockTool, + sap_s4hana_list_outbound_deliveries: sapS4HanaListOutboundDeliveriesTool, + sap_s4hana_list_products: sapS4HanaListProductsTool, + sap_s4hana_list_purchase_orders: sapS4HanaListPurchaseOrdersTool, + sap_s4hana_list_purchase_requisitions: sapS4HanaListPurchaseRequisitionsTool, + sap_s4hana_list_sales_orders: sapS4HanaListSalesOrdersTool, + sap_s4hana_list_supplier_invoices: sapS4HanaListSupplierInvoicesTool, + sap_s4hana_list_suppliers: sapS4HanaListSuppliersTool, + sap_s4hana_odata_query: sapS4HanaOdataQueryTool, + sap_s4hana_update_business_partner: sapS4HanaUpdateBusinessPartnerTool, + sap_s4hana_update_customer: sapS4HanaUpdateCustomerTool, + sap_s4hana_update_product: sapS4HanaUpdateProductTool, + sap_s4hana_update_purchase_order: sapS4HanaUpdatePurchaseOrderTool, + sap_s4hana_update_purchase_requisition: sapS4HanaUpdatePurchaseRequisitionTool, + sap_s4hana_update_sales_order: sapS4HanaUpdateSalesOrderTool, + sap_s4hana_update_supplier: sapS4HanaUpdateSupplierTool, sqs_send: sqsSendTool, sts_assume_role: stsAssumeRoleTool, sts_get_caller_identity: stsGetCallerIdentityTool, diff --git a/apps/sim/tools/sap_s4hana/create_business_partner.ts b/apps/sim/tools/sap_s4hana/create_business_partner.ts new file mode 100644 index 0000000000..c908a2e118 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/create_business_partner.ts @@ -0,0 +1,162 @@ +import type { CreateBusinessPartnerParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const createBusinessPartnerTool: ToolConfig = + { + id: 'sap_s4hana_create_business_partner', + name: 'SAP S/4HANA Create Business Partner', + description: + 'Create a business partner in SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner). For Person category 1 provide FirstName and LastName. For Organization category 2 provide OrganizationBPName1.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + businessPartnerCategory: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BusinessPartnerCategory: "1" Person, "2" Organization, "3" Group', + }, + businessPartnerGrouping: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'BusinessPartnerGrouping (number range / role grouping configured in S/4HANA, e.g. "0001")', + }, + firstName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'FirstName (required for Person)', + }, + lastName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'LastName (required for Person)', + }, + organizationBPName1: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OrganizationBPName1 (required for Organization)', + }, + body: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Optional additional A_BusinessPartner fields merged into the create payload', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const extra = parseJsonInput>(params.body, 'body') ?? {} + const extraHasName = (key: string) => Object.hasOwn(extra, key) && Boolean(extra[key]) + if (params.businessPartnerCategory === '1') { + const hasFirst = Boolean(params.firstName) || extraHasName('FirstName') + const hasLast = Boolean(params.lastName) || extraHasName('LastName') + if (!hasFirst || !hasLast) { + throw new Error('BusinessPartnerCategory "1" (Person) requires FirstName and LastName') + } + } else if (params.businessPartnerCategory === '2') { + const hasOrgName = + Boolean(params.organizationBPName1) || extraHasName('OrganizationBPName1') + if (!hasOrgName) { + throw new Error( + 'BusinessPartnerCategory "2" (Organization) requires OrganizationBPName1' + ) + } + } + const payload: Record = { + ...extra, + BusinessPartnerCategory: params.businessPartnerCategory, + BusinessPartnerGrouping: params.businessPartnerGrouping, + } + if (params.firstName) payload.FirstName = params.firstName + if (params.lastName) payload.LastName = params.lastName + if (params.organizationBPName1) payload.OrganizationBPName1 = params.organizationBPName1 + return { + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: '/A_BusinessPartner', + method: 'POST', + query: { $format: 'json' }, + body: payload, + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Created A_BusinessPartner entity' }, + }, + } diff --git a/apps/sim/tools/sap_s4hana/create_purchase_order.ts b/apps/sim/tools/sap_s4hana/create_purchase_order.ts new file mode 100644 index 0000000000..04dcc8662d --- /dev/null +++ b/apps/sim/tools/sap_s4hana/create_purchase_order.ts @@ -0,0 +1,145 @@ +import type { CreatePurchaseOrderParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const createPurchaseOrderTool: ToolConfig = { + id: 'sap_s4hana_create_purchase_order', + name: 'SAP S/4HANA Create Purchase Order', + description: + 'Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder). PurchaseOrder is auto-assigned by SAP from the document number range; provide line items via the body parameter.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + purchaseOrderType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchaseOrderType (e.g., "NB" Standard PO)', + }, + companyCode: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'CompanyCode (4 chars, e.g., "1010")', + }, + purchasingOrganization: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchasingOrganization (4 chars)', + }, + purchasingGroup: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchasingGroup (3 chars)', + }, + supplier: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Supplier business partner key (up to 10 chars)', + }, + body: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Additional A_PurchaseOrder fields and to_PurchaseOrderItem deep-insert items merged into the create payload (e.g., {"to_PurchaseOrderItem":[{"PurchaseOrderItem":"10","Material":"TG11","OrderQuantity":"5","Plant":"1010","PurchaseOrderQuantityUnit":"PC","NetPriceAmount":"100.00","DocumentCurrency":"USD"}]}).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const extra = parseJsonInput>(params.body, 'body') ?? {} + const payload: Record = { + ...extra, + PurchaseOrderType: params.purchaseOrderType, + CompanyCode: params.companyCode, + PurchasingOrganization: params.purchasingOrganization, + PurchasingGroup: params.purchasingGroup, + Supplier: params.supplier, + } + return { + ...baseProxyBody(params), + service: 'API_PURCHASEORDER_PROCESS_SRV', + path: '/A_PurchaseOrder', + method: 'POST', + query: { $format: 'json' }, + body: payload, + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Created A_PurchaseOrder entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/create_purchase_requisition.ts b/apps/sim/tools/sap_s4hana/create_purchase_requisition.ts new file mode 100644 index 0000000000..81b27334d2 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/create_purchase_requisition.ts @@ -0,0 +1,132 @@ +import type { CreatePurchaseRequisitionParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const createPurchaseRequisitionTool: ToolConfig< + CreatePurchaseRequisitionParams, + SapProxyResponse +> = { + id: 'sap_s4hana_create_purchase_requisition', + name: 'SAP S/4HANA Create Purchase Requisition', + description: + 'Create a purchase requisition in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). PurchaseRequisition is auto-assigned by SAP from the document number range; provide line items via the to_PurchaseReqnItem deep-insert array. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + purchaseRequisitionType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchaseRequisitionType (e.g., "NB" Standard PR)', + }, + items: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'to_PurchaseReqnItem deep-insert array (e.g., [{"PurchaseRequisitionItem":"10","Material":"TG11","RequestedQuantity":"5","Plant":"1010","BaseUnit":"PC","DeliveryDate":"/Date(1735689600000)/"}])', + }, + body: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Additional A_PurchaseRequisitionHeader fields merged into the create payload (e.g., {"PurchaseRequisitionDescription":"Office supplies"})', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const items = parseJsonInput>>(params.items, 'items') + if (!Array.isArray(items) || items.length === 0) { + throw new Error('items must be a non-empty JSON array of purchase requisition items') + } + const extra = parseJsonInput>(params.body, 'body') ?? {} + const payload: Record = { + ...extra, + PurchaseRequisitionType: params.purchaseRequisitionType, + to_PurchaseReqnItem: items, + } + return { + ...baseProxyBody(params), + service: 'API_PURCHASEREQ_PROCESS_SRV', + path: '/A_PurchaseRequisitionHeader', + method: 'POST', + query: { $format: 'json' }, + body: payload, + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Created A_PurchaseRequisitionHeader entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/create_sales_order.ts b/apps/sim/tools/sap_s4hana/create_sales_order.ts new file mode 100644 index 0000000000..fc63d86dd2 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/create_sales_order.ts @@ -0,0 +1,159 @@ +import type { CreateSalesOrderParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const createSalesOrderTool: ToolConfig = { + id: 'sap_s4hana_create_sales_order', + name: 'SAP S/4HANA Create Sales Order', + description: + 'Create a sales order in SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with deep insert of sales order items via to_Item.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + salesOrderType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SalesOrderType (e.g., "OR" Standard Order)', + }, + salesOrganization: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SalesOrganization (4 chars, e.g., "1010")', + }, + distributionChannel: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'DistributionChannel (2 chars, e.g., "10")', + }, + organizationDivision: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'OrganizationDivision (2 chars, e.g., "00")', + }, + soldToParty: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SoldToParty business partner key (up to 10 chars)', + }, + items: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'Array of sales order items for to_Item deep insert. Each item should include Material and RequestedQuantity (e.g., [{"Material":"TG11","RequestedQuantity":"1"}]).', + }, + body: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Optional additional A_SalesOrder fields merged into the create payload', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const items = parseJsonInput>>(params.items, 'items') + if (!Array.isArray(items)) { + throw new Error('items must be a JSON array of sales order item objects') + } + const extra = parseJsonInput>(params.body, 'body') ?? {} + const payload: Record = { + ...extra, + SalesOrderType: params.salesOrderType, + SalesOrganization: params.salesOrganization, + DistributionChannel: params.distributionChannel, + OrganizationDivision: params.organizationDivision, + SoldToParty: params.soldToParty, + to_Item: items, + } + return { + ...baseProxyBody(params), + service: 'API_SALES_ORDER_SRV', + path: '/A_SalesOrder', + method: 'POST', + query: { $format: 'json' }, + body: payload, + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { + type: 'json', + description: 'Created A_SalesOrder entity (with deep-inserted items if expanded by SAP)', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/delete_sales_order.ts b/apps/sim/tools/sap_s4hana/delete_sales_order.ts new file mode 100644 index 0000000000..a353d28807 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/delete_sales_order.ts @@ -0,0 +1,108 @@ +import type { DeleteSalesOrderParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const deleteSalesOrderTool: ToolConfig = { + id: 'sap_s4hana_delete_sales_order', + name: 'SAP S/4HANA Delete Sales Order', + description: + 'Delete an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Only orders without subsequent documents (deliveries, invoices) can be deleted; otherwise reject items via update instead.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + salesOrder: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SalesOrder key to delete (string, up to 10 characters)', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_SALES_ORDER_SRV', + path: `/A_SalesOrder(${quoteOdataKey(params.salesOrder)})`, + method: 'DELETE', + ifMatch: params.ifMatch || '*', + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { type: 'json', description: 'Null on successful deletion' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_billing_document.ts b/apps/sim/tools/sap_s4hana/get_billing_document.ts new file mode 100644 index 0000000000..7a2d9047ac --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_billing_document.ts @@ -0,0 +1,115 @@ +import type { GetBillingDocumentParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getBillingDocumentTool: ToolConfig = { + id: 'sap_s4hana_get_billing_document', + name: 'SAP S/4HANA Get Billing Document', + description: + 'Retrieve a single billing document (customer invoice) by BillingDocument key from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + billingDocument: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BillingDocument key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_Item,to_Partner")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BILLING_DOCUMENT_SRV', + path: `/A_BillingDocument(${quoteOdataKey(params.billingDocument)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_BillingDocument entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_business_partner.ts b/apps/sim/tools/sap_s4hana/get_business_partner.ts new file mode 100644 index 0000000000..c7f5d10200 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_business_partner.ts @@ -0,0 +1,115 @@ +import type { GetBusinessPartnerParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getBusinessPartnerTool: ToolConfig = { + id: 'sap_s4hana_get_business_partner', + name: 'SAP S/4HANA Get Business Partner', + description: + 'Retrieve a single business partner by BusinessPartner key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + businessPartner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BusinessPartner key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand ($expand)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: `/A_BusinessPartner(${quoteOdataKey(params.businessPartner)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_BusinessPartner entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_customer.ts b/apps/sim/tools/sap_s4hana/get_customer.ts new file mode 100644 index 0000000000..ca03c2e438 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_customer.ts @@ -0,0 +1,116 @@ +import type { GetCustomerParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getCustomerTool: ToolConfig = { + id: 'sap_s4hana_get_customer', + name: 'SAP S/4HANA Get Customer', + description: + 'Retrieve a single customer by Customer key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + customer: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Customer key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_CustomerCompany,to_CustomerSalesArea")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: `/A_Customer(${quoteOdataKey(params.customer)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_Customer entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_inbound_delivery.ts b/apps/sim/tools/sap_s4hana/get_inbound_delivery.ts new file mode 100644 index 0000000000..78d78a2459 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_inbound_delivery.ts @@ -0,0 +1,116 @@ +import type { GetInboundDeliveryParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getInboundDeliveryTool: ToolConfig = { + id: 'sap_s4hana_get_inbound_delivery', + name: 'SAP S/4HANA Get Inbound Delivery', + description: + 'Retrieve a single inbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + deliveryDocument: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'DeliveryDocument key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_DeliveryDocumentItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_INBOUND_DELIVERY_SRV;v=0002', + path: `/A_InbDeliveryHeader(${quoteOdataKey(params.deliveryDocument)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_InbDeliveryHeader entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_outbound_delivery.ts b/apps/sim/tools/sap_s4hana/get_outbound_delivery.ts new file mode 100644 index 0000000000..eaa90818e3 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_outbound_delivery.ts @@ -0,0 +1,116 @@ +import type { GetOutboundDeliveryParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getOutboundDeliveryTool: ToolConfig = { + id: 'sap_s4hana_get_outbound_delivery', + name: 'SAP S/4HANA Get Outbound Delivery', + description: + 'Retrieve a single outbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + deliveryDocument: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'DeliveryDocument key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_DeliveryDocumentItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_OUTBOUND_DELIVERY_SRV;v=0002', + path: `/A_OutbDeliveryHeader(${quoteOdataKey(params.deliveryDocument)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_OutbDeliveryHeader entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_product.ts b/apps/sim/tools/sap_s4hana/get_product.ts new file mode 100644 index 0000000000..c9c9cd13d4 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_product.ts @@ -0,0 +1,115 @@ +import type { GetProductParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getProductTool: ToolConfig = { + id: 'sap_s4hana_get_product', + name: 'SAP S/4HANA Get Product', + description: + 'Retrieve a single product (material) by Product key from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + product: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Product key (string, up to 40 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_Description")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_PRODUCT_SRV', + path: `/A_Product(${quoteOdataKey(params.product)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_Product entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_purchase_order.ts b/apps/sim/tools/sap_s4hana/get_purchase_order.ts new file mode 100644 index 0000000000..3a97e27211 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_purchase_order.ts @@ -0,0 +1,115 @@ +import type { GetPurchaseOrderParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getPurchaseOrderTool: ToolConfig = { + id: 'sap_s4hana_get_purchase_order', + name: 'SAP S/4HANA Get Purchase Order', + description: + 'Retrieve a single purchase order by PurchaseOrder key from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + purchaseOrder: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchaseOrder key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_PurchaseOrderItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_PURCHASEORDER_PROCESS_SRV', + path: `/A_PurchaseOrder(${quoteOdataKey(params.purchaseOrder)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_PurchaseOrder entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_purchase_requisition.ts b/apps/sim/tools/sap_s4hana/get_purchase_requisition.ts new file mode 100644 index 0000000000..518f249948 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_purchase_requisition.ts @@ -0,0 +1,118 @@ +import type { GetPurchaseRequisitionParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getPurchaseRequisitionTool: ToolConfig< + GetPurchaseRequisitionParams, + SapProxyResponse +> = { + id: 'sap_s4hana_get_purchase_requisition', + name: 'SAP S/4HANA Get Purchase Requisition', + description: + 'Retrieve a single purchase requisition by PurchaseRequisition key from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + purchaseRequisition: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchaseRequisition key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_PurchaseReqnItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_PURCHASEREQ_PROCESS_SRV', + path: `/A_PurchaseRequisitionHeader(${quoteOdataKey(params.purchaseRequisition)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_PurchaseRequisitionHeader entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_sales_order.ts b/apps/sim/tools/sap_s4hana/get_sales_order.ts new file mode 100644 index 0000000000..f164582081 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_sales_order.ts @@ -0,0 +1,115 @@ +import type { GetSalesOrderParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getSalesOrderTool: ToolConfig = { + id: 'sap_s4hana_get_sales_order', + name: 'SAP S/4HANA Get Sales Order', + description: + 'Retrieve a single sales order by SalesOrder key from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + salesOrder: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SalesOrder key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_Item")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_SALES_ORDER_SRV', + path: `/A_SalesOrder(${quoteOdataKey(params.salesOrder)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_SalesOrder entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_supplier.ts b/apps/sim/tools/sap_s4hana/get_supplier.ts new file mode 100644 index 0000000000..ebe31ade5a --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_supplier.ts @@ -0,0 +1,116 @@ +import type { GetSupplierParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getSupplierTool: ToolConfig = { + id: 'sap_s4hana_get_supplier', + name: 'SAP S/4HANA Get Supplier', + description: + 'Retrieve a single supplier by Supplier key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + supplier: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Supplier key (string, up to 10 characters)', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_SupplierCompany,to_SupplierPurchasingOrg")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: `/A_Supplier(${quoteOdataKey(params.supplier)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_Supplier entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/get_supplier_invoice.ts b/apps/sim/tools/sap_s4hana/get_supplier_invoice.ts new file mode 100644 index 0000000000..9e5c3ac953 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/get_supplier_invoice.ts @@ -0,0 +1,121 @@ +import type { GetSupplierInvoiceParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildEntityQuery, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const getSupplierInvoiceTool: ToolConfig = { + id: 'sap_s4hana_get_supplier_invoice', + name: 'SAP S/4HANA Get Supplier Invoice', + description: + 'Retrieve a single supplier invoice by composite key (SupplierInvoice + FiscalYear) from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice).', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + supplierInvoice: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SupplierInvoice key (string, up to 10 characters)', + }, + fiscalYear: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'FiscalYear (4-character year, e.g., "2024")', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand ($expand)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_SUPPLIERINVOICE_PROCESS_SRV', + path: `/A_SupplierInvoice(SupplierInvoice=${quoteOdataKey(params.supplierInvoice)},FiscalYear=${quoteOdataKey(params.fiscalYear)})`, + method: 'GET', + query: buildEntityQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'A_SupplierInvoice entity' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/index.ts b/apps/sim/tools/sap_s4hana/index.ts new file mode 100644 index 0000000000..0115fdfe7c --- /dev/null +++ b/apps/sim/tools/sap_s4hana/index.ts @@ -0,0 +1,37 @@ +export { createBusinessPartnerTool } from '@/tools/sap_s4hana/create_business_partner' +export { createPurchaseOrderTool } from '@/tools/sap_s4hana/create_purchase_order' +export { createPurchaseRequisitionTool } from '@/tools/sap_s4hana/create_purchase_requisition' +export { createSalesOrderTool } from '@/tools/sap_s4hana/create_sales_order' +export { deleteSalesOrderTool } from '@/tools/sap_s4hana/delete_sales_order' +export { getBillingDocumentTool } from '@/tools/sap_s4hana/get_billing_document' +export { getBusinessPartnerTool } from '@/tools/sap_s4hana/get_business_partner' +export { getCustomerTool } from '@/tools/sap_s4hana/get_customer' +export { getInboundDeliveryTool } from '@/tools/sap_s4hana/get_inbound_delivery' +export { getOutboundDeliveryTool } from '@/tools/sap_s4hana/get_outbound_delivery' +export { getProductTool } from '@/tools/sap_s4hana/get_product' +export { getPurchaseOrderTool } from '@/tools/sap_s4hana/get_purchase_order' +export { getPurchaseRequisitionTool } from '@/tools/sap_s4hana/get_purchase_requisition' +export { getSalesOrderTool } from '@/tools/sap_s4hana/get_sales_order' +export { getSupplierTool } from '@/tools/sap_s4hana/get_supplier' +export { getSupplierInvoiceTool } from '@/tools/sap_s4hana/get_supplier_invoice' +export { listBillingDocumentsTool } from '@/tools/sap_s4hana/list_billing_documents' +export { listBusinessPartnersTool } from '@/tools/sap_s4hana/list_business_partners' +export { listCustomersTool } from '@/tools/sap_s4hana/list_customers' +export { listInboundDeliveriesTool } from '@/tools/sap_s4hana/list_inbound_deliveries' +export { listMaterialDocumentsTool } from '@/tools/sap_s4hana/list_material_documents' +export { listMaterialStockTool } from '@/tools/sap_s4hana/list_material_stock' +export { listOutboundDeliveriesTool } from '@/tools/sap_s4hana/list_outbound_deliveries' +export { listProductsTool } from '@/tools/sap_s4hana/list_products' +export { listPurchaseOrdersTool } from '@/tools/sap_s4hana/list_purchase_orders' +export { listPurchaseRequisitionsTool } from '@/tools/sap_s4hana/list_purchase_requisitions' +export { listSalesOrdersTool } from '@/tools/sap_s4hana/list_sales_orders' +export { listSupplierInvoicesTool } from '@/tools/sap_s4hana/list_supplier_invoices' +export { listSuppliersTool } from '@/tools/sap_s4hana/list_suppliers' +export { odataQueryTool } from '@/tools/sap_s4hana/odata_query' +export { updateBusinessPartnerTool } from '@/tools/sap_s4hana/update_business_partner' +export { updateCustomerTool } from '@/tools/sap_s4hana/update_customer' +export { updateProductTool } from '@/tools/sap_s4hana/update_product' +export { updatePurchaseOrderTool } from '@/tools/sap_s4hana/update_purchase_order' +export { updatePurchaseRequisitionTool } from '@/tools/sap_s4hana/update_purchase_requisition' +export { updateSalesOrderTool } from '@/tools/sap_s4hana/update_sales_order' +export { updateSupplierTool } from '@/tools/sap_s4hana/update_supplier' diff --git a/apps/sim/tools/sap_s4hana/list_billing_documents.ts b/apps/sim/tools/sap_s4hana/list_billing_documents.ts new file mode 100644 index 0000000000..dfba1c08d9 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_billing_documents.ts @@ -0,0 +1,132 @@ +import type { ListBillingDocumentsParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listBillingDocumentsTool: ToolConfig = { + id: 'sap_s4hana_list_billing_documents', + name: 'SAP S/4HANA List Billing Documents', + description: + 'List billing documents (customer invoices) from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "SoldToParty eq \'10100001\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_Item,to_Partner")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BILLING_DOCUMENT_SRV', + path: '/A_BillingDocument', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_BillingDocument entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_business_partners.ts b/apps/sim/tools/sap_s4hana/list_business_partners.ts new file mode 100644 index 0000000000..4f90446e4d --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_business_partners.ts @@ -0,0 +1,132 @@ +import type { ListBusinessPartnersParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listBusinessPartnersTool: ToolConfig = { + id: 'sap_s4hana_list_business_partners', + name: 'SAP S/4HANA List Business Partners', + description: + 'List business partners from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "BusinessPartnerCategory eq \'1\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand ($expand)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: '/A_BusinessPartner', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_BusinessPartner entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_customers.ts b/apps/sim/tools/sap_s4hana/list_customers.ts new file mode 100644 index 0000000000..1cc7f6a34a --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_customers.ts @@ -0,0 +1,133 @@ +import type { ListCustomersParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listCustomersTool: ToolConfig = { + id: 'sap_s4hana_list_customers', + name: 'SAP S/4HANA List Customers', + description: + 'List customers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "CustomerAccountGroup eq \'Z001\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_CustomerCompany,to_CustomerSalesArea")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: '/A_Customer', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_Customer entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_inbound_deliveries.ts b/apps/sim/tools/sap_s4hana/list_inbound_deliveries.ts new file mode 100644 index 0000000000..b04a905b0d --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_inbound_deliveries.ts @@ -0,0 +1,134 @@ +import type { ListInboundDeliveriesParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listInboundDeliveriesTool: ToolConfig = + { + id: 'sap_s4hana_list_inbound_deliveries', + name: 'SAP S/4HANA List Inbound Deliveries', + description: + 'List inbound deliveries from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "ReceivingPlant eq \'1010\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_DeliveryDocumentItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_INBOUND_DELIVERY_SRV;v=0002', + path: '/A_InbDeliveryHeader', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_InbDeliveryHeader entities' }, + }, + } diff --git a/apps/sim/tools/sap_s4hana/list_material_documents.ts b/apps/sim/tools/sap_s4hana/list_material_documents.ts new file mode 100644 index 0000000000..6f9a254e6b --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_material_documents.ts @@ -0,0 +1,135 @@ +import type { ListMaterialDocumentsParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listMaterialDocumentsTool: ToolConfig = + { + id: 'sap_s4hana_list_material_documents', + name: 'SAP S/4HANA List Material Documents', + description: + 'List material document headers (goods movements) from SAP S/4HANA Cloud (API_MATERIAL_DOCUMENT_SRV, A_MaterialDocumentHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + "OData $filter expression (e.g., \"MaterialDocumentYear eq '2024' and PostingDate ge datetime'2024-01-01T00:00:00'\")", + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_MaterialDocumentItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_MATERIAL_DOCUMENT_SRV', + path: '/A_MaterialDocumentHeader', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_MaterialDocumentHeader entities' }, + }, + } diff --git a/apps/sim/tools/sap_s4hana/list_material_stock.ts b/apps/sim/tools/sap_s4hana/list_material_stock.ts new file mode 100644 index 0000000000..0d5159a1f0 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_material_stock.ts @@ -0,0 +1,133 @@ +import type { ListMaterialStockParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listMaterialStockTool: ToolConfig = { + id: 'sap_s4hana_list_material_stock', + name: 'SAP S/4HANA List Material Stock', + description: + 'List material stock quantities from SAP S/4HANA Cloud (API_MATERIAL_STOCK_SRV, A_MatlStkInAcctMod). The entity uses an 11-field composite key (Material, Plant, StorageLocation, Batch, Supplier, Customer, WBSElementInternalID, SDDocument, SDDocumentItem, InventorySpecialStockType, InventoryStockType) — query with $filter on these fields instead of a direct key lookup.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + "OData $filter expression (e.g., \"Material eq 'TG10' and Plant eq '1010' and InventoryStockType eq '01'\")", + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand ($expand)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_MATERIAL_STOCK_SRV', + path: '/A_MatlStkInAcctMod', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_MatlStkInAcctMod stock entries' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_outbound_deliveries.ts b/apps/sim/tools/sap_s4hana/list_outbound_deliveries.ts new file mode 100644 index 0000000000..a284133b00 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_outbound_deliveries.ts @@ -0,0 +1,136 @@ +import type { ListOutboundDeliveriesParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listOutboundDeliveriesTool: ToolConfig< + ListOutboundDeliveriesParams, + SapProxyResponse +> = { + id: 'sap_s4hana_list_outbound_deliveries', + name: 'SAP S/4HANA List Outbound Deliveries', + description: + 'List outbound deliveries from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "OverallDeliveryStatus eq \'C\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_DeliveryDocumentItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_OUTBOUND_DELIVERY_SRV;v=0002', + path: '/A_OutbDeliveryHeader', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_OutbDeliveryHeader entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_products.ts b/apps/sim/tools/sap_s4hana/list_products.ts new file mode 100644 index 0000000000..3d624e2f78 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_products.ts @@ -0,0 +1,132 @@ +import type { ListProductsParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listProductsTool: ToolConfig = { + id: 'sap_s4hana_list_products', + name: 'SAP S/4HANA List Products', + description: + 'List products (materials) from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "ProductType eq \'FERT\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand ($expand)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_PRODUCT_SRV', + path: '/A_Product', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_Product entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_purchase_orders.ts b/apps/sim/tools/sap_s4hana/list_purchase_orders.ts new file mode 100644 index 0000000000..f3e2c7778d --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_purchase_orders.ts @@ -0,0 +1,132 @@ +import type { ListPurchaseOrdersParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listPurchaseOrdersTool: ToolConfig = { + id: 'sap_s4hana_list_purchase_orders', + name: 'SAP S/4HANA List Purchase Orders', + description: + 'List purchase orders from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "CompanyCode eq \'1010\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_PurchaseOrderItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_PURCHASEORDER_PROCESS_SRV', + path: '/A_PurchaseOrder', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_PurchaseOrder entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_purchase_requisitions.ts b/apps/sim/tools/sap_s4hana/list_purchase_requisitions.ts new file mode 100644 index 0000000000..88f99c71b1 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_purchase_requisitions.ts @@ -0,0 +1,135 @@ +import type { ListPurchaseRequisitionsParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listPurchaseRequisitionsTool: ToolConfig< + ListPurchaseRequisitionsParams, + SapProxyResponse +> = { + id: 'sap_s4hana_list_purchase_requisitions', + name: 'SAP S/4HANA List Purchase Requisitions', + description: + 'List purchase requisitions from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "PurchaseRequisitionType eq \'NB\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_PurchaseReqnItem")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_PURCHASEREQ_PROCESS_SRV', + path: '/A_PurchaseRequisitionHeader', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_PurchaseRequisitionHeader entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_sales_orders.ts b/apps/sim/tools/sap_s4hana/list_sales_orders.ts new file mode 100644 index 0000000000..75f795fac9 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_sales_orders.ts @@ -0,0 +1,132 @@ +import type { ListSalesOrdersParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listSalesOrdersTool: ToolConfig = { + id: 'sap_s4hana_list_sales_orders', + name: 'SAP S/4HANA List Sales Orders', + description: + 'List sales orders from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "SalesOrganization eq \'1010\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand (e.g., "to_Item,to_Partner")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_SALES_ORDER_SRV', + path: '/A_SalesOrder', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_SalesOrder entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_supplier_invoices.ts b/apps/sim/tools/sap_s4hana/list_supplier_invoices.ts new file mode 100644 index 0000000000..2415509f95 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_supplier_invoices.ts @@ -0,0 +1,132 @@ +import type { ListSupplierInvoicesParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listSupplierInvoicesTool: ToolConfig = { + id: 'sap_s4hana_list_supplier_invoices', + name: 'SAP S/4HANA List Supplier Invoices', + description: + 'List supplier invoices from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "InvoicingParty eq \'17300001\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated navigation properties to expand ($expand)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_SUPPLIERINVOICE_PROCESS_SRV', + path: '/A_SupplierInvoice', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_SupplierInvoice entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/list_suppliers.ts b/apps/sim/tools/sap_s4hana/list_suppliers.ts new file mode 100644 index 0000000000..cd22519973 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/list_suppliers.ts @@ -0,0 +1,133 @@ +import type { ListSuppliersParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + buildOdataQuery, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const listSuppliersTool: ToolConfig = { + id: 'sap_s4hana_list_suppliers', + name: 'SAP S/4HANA List Suppliers', + description: + 'List suppliers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier) with optional OData $filter, $top, $skip, $orderby, $select, $expand.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + filter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $filter expression (e.g., "SupplierAccountGroup eq \'BP02\'")', + }, + top: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum results to return ($top)', + }, + skip: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip ($skip)', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'OData $orderby expression', + }, + select: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated fields to return ($select)', + }, + expand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated navigation properties to expand (e.g., "to_SupplierCompany,to_SupplierPurchasingOrg")', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: '/A_Supplier', + method: 'GET', + query: buildOdataQuery(params), + }), + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { type: 'json', description: 'Array of A_Supplier entities' }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/odata_query.ts b/apps/sim/tools/sap_s4hana/odata_query.ts new file mode 100644 index 0000000000..770eb42ef2 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/odata_query.ts @@ -0,0 +1,163 @@ +import type { ODataQueryParams, SapProxyResponse } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +function normalizeQuery( + query: ODataQueryParams['query'] +): Record | undefined { + if (!query) return undefined + if (typeof query === 'object') return query + if (typeof query !== 'string') return undefined + const trimmed = query.trim() + if (!trimmed) return undefined + if (trimmed.startsWith('{')) { + return parseJsonInput>(trimmed, 'query') + } + const search = new URLSearchParams(trimmed.startsWith('?') ? trimmed.slice(1) : trimmed) + const result: Record = {} + for (const [key, value] of search.entries()) result[key] = value + return result +} + +export const odataQueryTool: ToolConfig = { + id: 'sap_s4hana_odata_query', + name: 'SAP S/4HANA OData Query', + description: + 'Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + service: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'OData service name (e.g., "API_BUSINESS_PARTNER", "API_SALES_ORDER_SRV")', + }, + path: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Path inside the service (e.g., "/A_BusinessPartner" or "/A_BusinessPartner(\'1000123\')")', + }, + method: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'HTTP method: GET (default), POST, PATCH, PUT, DELETE, MERGE', + }, + query: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'OData query parameters as JSON object or query string (e.g., {"$filter":"BusinessPartnerCategory eq \'1\'","$top":10}). $format=json is added automatically when omitted.', + }, + body: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON request body for write operations', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'ETag value for the If-Match header (required by SAP for PATCH/PUT/DELETE on existing entities)', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const query = normalizeQuery(params.query) ?? {} + if (!('$format' in query)) query.$format = 'json' + const requestBody: Record = { + ...baseProxyBody(params), + service: params.service, + path: params.path, + method: params.method || 'GET', + query, + } + const parsedBody = parseJsonInput>(params.body, 'body') + if (parsedBody !== undefined) requestBody.body = parsedBody + if (params.ifMatch) requestBody.ifMatch = params.ifMatch + return requestBody + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP' }, + data: { + type: 'json', + description: 'Parsed OData payload (entity, collection, or null on 204)', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/types.ts b/apps/sim/tools/sap_s4hana/types.ts new file mode 100644 index 0000000000..c8103a212e --- /dev/null +++ b/apps/sim/tools/sap_s4hana/types.ts @@ -0,0 +1,302 @@ +import type { ToolResponse } from '@/tools/types' + +export type SapDeploymentType = 'cloud_public' | 'cloud_private' | 'on_premise' +export type SapAuthType = 'oauth_client_credentials' | 'basic' + +export interface SapBaseParams { + deploymentType?: SapDeploymentType + authType?: SapAuthType + subdomain?: string + region?: string + baseUrl?: string + tokenUrl?: string + clientId?: string + clientSecret?: string + username?: string + password?: string +} + +export interface ProxyOutput { + status: number + data: unknown +} + +export interface SapProxyResponse extends ToolResponse { + output: ProxyOutput +} + +export interface ListBusinessPartnersParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetBusinessPartnerParams extends SapBaseParams { + businessPartner: string + select?: string + expand?: string +} + +export interface CreateBusinessPartnerParams extends SapBaseParams { + businessPartnerCategory: string + businessPartnerGrouping: string + firstName?: string + lastName?: string + organizationBPName1?: string + body?: Record | string +} + +export interface ListSalesOrdersParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetSalesOrderParams extends SapBaseParams { + salesOrder: string + select?: string + expand?: string +} + +export interface CreateSalesOrderParams extends SapBaseParams { + salesOrderType: string + salesOrganization: string + distributionChannel: string + organizationDivision: string + soldToParty: string + items: string | Array> + body?: Record | string +} + +export interface ListProductsParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetProductParams extends SapBaseParams { + product: string + select?: string + expand?: string +} + +export interface ListPurchaseOrdersParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetPurchaseOrderParams extends SapBaseParams { + purchaseOrder: string + select?: string + expand?: string +} + +export interface CreatePurchaseOrderParams extends SapBaseParams { + purchaseOrderType: string + companyCode: string + purchasingOrganization: string + purchasingGroup: string + supplier: string + body?: Record | string +} + +export interface ListSupplierInvoicesParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetSupplierInvoiceParams extends SapBaseParams { + supplierInvoice: string + fiscalYear: string + select?: string + expand?: string +} + +export interface ListOutboundDeliveriesParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetOutboundDeliveryParams extends SapBaseParams { + deliveryDocument: string + select?: string + expand?: string +} + +export interface ListBillingDocumentsParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetBillingDocumentParams extends SapBaseParams { + billingDocument: string + select?: string + expand?: string +} + +export interface ListPurchaseRequisitionsParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetPurchaseRequisitionParams extends SapBaseParams { + purchaseRequisition: string + select?: string + expand?: string +} + +export interface ListMaterialStockParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface ListSuppliersParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetSupplierParams extends SapBaseParams { + supplier: string + select?: string + expand?: string +} + +export interface ListCustomersParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetCustomerParams extends SapBaseParams { + customer: string + select?: string + expand?: string +} + +export interface ListInboundDeliveriesParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface GetInboundDeliveryParams extends SapBaseParams { + deliveryDocument: string + select?: string + expand?: string +} + +export interface ListMaterialDocumentsParams extends SapBaseParams { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +} + +export interface UpdateBusinessPartnerParams extends SapBaseParams { + businessPartner: string + body: Record | string + ifMatch?: string +} + +export interface UpdateCustomerParams extends SapBaseParams { + customer: string + body: Record | string + ifMatch?: string +} + +export interface UpdateSupplierParams extends SapBaseParams { + supplier: string + body: Record | string + ifMatch?: string +} + +export interface UpdateProductParams extends SapBaseParams { + product: string + body: Record | string + ifMatch?: string +} + +export interface UpdateSalesOrderParams extends SapBaseParams { + salesOrder: string + body: Record | string + ifMatch?: string +} + +export interface DeleteSalesOrderParams extends SapBaseParams { + salesOrder: string + ifMatch?: string +} + +export interface UpdatePurchaseOrderParams extends SapBaseParams { + purchaseOrder: string + body: Record | string + ifMatch?: string +} + +export interface UpdatePurchaseRequisitionParams extends SapBaseParams { + purchaseRequisition: string + body: Record | string + ifMatch?: string +} + +export interface CreatePurchaseRequisitionParams extends SapBaseParams { + purchaseRequisitionType: string + items: string | Array> + body?: Record | string +} + +export type ODataMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' | 'MERGE' + +export interface ODataQueryParams extends SapBaseParams { + service: string + path: string + method?: ODataMethod + query?: string | Record + body?: Record | string + ifMatch?: string +} diff --git a/apps/sim/tools/sap_s4hana/update_business_partner.ts b/apps/sim/tools/sap_s4hana/update_business_partner.ts new file mode 100644 index 0000000000..551391bb27 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_business_partner.ts @@ -0,0 +1,128 @@ +import type { SapProxyResponse, UpdateBusinessPartnerParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateBusinessPartnerTool: ToolConfig = + { + id: 'sap_s4hana_update_business_partner', + name: 'SAP S/4HANA Update Business Partner', + description: + 'Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + businessPartner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BusinessPartner key to update (string, up to 10 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_BusinessPartner fields to update (e.g., {"FirstName":"Jane","SearchTerm1":"VIP"})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: `/A_BusinessPartner(${quoteOdataKey(params.businessPartner)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: 'Null on 204 success, or updated A_BusinessPartner entity if SAP returns one', + }, + }, + } diff --git a/apps/sim/tools/sap_s4hana/update_customer.ts b/apps/sim/tools/sap_s4hana/update_customer.ts new file mode 100644 index 0000000000..3cee87f697 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_customer.ts @@ -0,0 +1,127 @@ +import type { SapProxyResponse, UpdateCustomerParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateCustomerTool: ToolConfig = { + id: 'sap_s4hana_update_customer', + name: 'SAP S/4HANA Update Customer', + description: + 'Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. Note: API_BUSINESS_PARTNER limits A_Customer PATCH to a small set of modifiable fields (e.g., OrderIsBlockedForCustomer, DeliveryIsBlock, BillingIsBlockedForCustomer, PostingIsBlocked, DeletionIndicator). Company-code attributes like PaymentBlockingReason live on A_CustomerCompany. Most descriptive customer attributes are read-only here and must be updated via the BusinessPartner entity. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + customer: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Customer key to update (string, up to 10 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_Customer fields to update (e.g., {"OrderIsBlockedForCustomer":true,"DeletionIndicator":false})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: `/A_Customer(${quoteOdataKey(params.customer)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: 'Null on 204 success, or updated A_Customer entity if SAP returns one', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/update_product.ts b/apps/sim/tools/sap_s4hana/update_product.ts new file mode 100644 index 0000000000..d129cd7218 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_product.ts @@ -0,0 +1,127 @@ +import type { SapProxyResponse, UpdateProductParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateProductTool: ToolConfig = { + id: 'sap_s4hana_update_product', + name: 'SAP S/4HANA Update Product', + description: + 'Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PATCH only sends the fields you provide; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV PATCH/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + product: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Product key to update (string, up to 40 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_Product fields to update (e.g., {"ProductGroup":"L001","IsMarkedForDeletion":false})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_PRODUCT_SRV', + path: `/A_Product(${quoteOdataKey(params.product)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: 'Null on 204 success, or updated A_Product entity if SAP returns one', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/update_purchase_order.ts b/apps/sim/tools/sap_s4hana/update_purchase_order.ts new file mode 100644 index 0000000000..f41cb33e4f --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_purchase_order.ts @@ -0,0 +1,127 @@ +import type { SapProxyResponse, UpdatePurchaseOrderParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updatePurchaseOrderTool: ToolConfig = { + id: 'sap_s4hana_update_purchase_order', + name: 'SAP S/4HANA Update Purchase Order', + description: + 'Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + purchaseOrder: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchaseOrder key to update (string, up to 10 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_PurchaseOrder fields to update (e.g., {"PurchasingGroup":"002","PurchaseOrderDate":"/Date(1735689600000)/"})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_PURCHASEORDER_PROCESS_SRV', + path: `/A_PurchaseOrder(${quoteOdataKey(params.purchaseOrder)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: 'Null on 204 success, or updated A_PurchaseOrder entity if SAP returns one', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/update_purchase_requisition.ts b/apps/sim/tools/sap_s4hana/update_purchase_requisition.ts new file mode 100644 index 0000000000..63f254e677 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_purchase_requisition.ts @@ -0,0 +1,131 @@ +import type { SapProxyResponse, UpdatePurchaseRequisitionParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updatePurchaseRequisitionTool: ToolConfig< + UpdatePurchaseRequisitionParams, + SapProxyResponse +> = { + id: 'sap_s4hana_update_purchase_requisition', + name: 'SAP S/4HANA Update Purchase Requisition', + description: + 'Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + purchaseRequisition: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PurchaseRequisition key to update (string, up to 10 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_PurchaseRequisitionHeader fields to update (e.g., {"PurchaseRequisitionType":"NB"})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_PURCHASEREQ_PROCESS_SRV', + path: `/A_PurchaseRequisitionHeader(${quoteOdataKey(params.purchaseRequisition)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: + 'Null on 204 success, or updated A_PurchaseRequisitionHeader entity if SAP returns one', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/update_sales_order.ts b/apps/sim/tools/sap_s4hana/update_sales_order.ts new file mode 100644 index 0000000000..8a5db8fe49 --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_sales_order.ts @@ -0,0 +1,127 @@ +import type { SapProxyResponse, UpdateSalesOrderParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateSalesOrderTool: ToolConfig = { + id: 'sap_s4hana_update_sales_order', + name: 'SAP S/4HANA Update Sales Order', + description: + 'Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + salesOrder: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'SalesOrder key to update (string, up to 10 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_SalesOrder fields to update (e.g., {"PurchaseOrderByCustomer":"PO-12345","HeaderBillingBlockReason":"01"})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_SALES_ORDER_SRV', + path: `/A_SalesOrder(${quoteOdataKey(params.salesOrder)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: 'Null on 204 success, or updated A_SalesOrder entity if SAP returns one', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/update_supplier.ts b/apps/sim/tools/sap_s4hana/update_supplier.ts new file mode 100644 index 0000000000..6412b8e4bb --- /dev/null +++ b/apps/sim/tools/sap_s4hana/update_supplier.ts @@ -0,0 +1,127 @@ +import type { SapProxyResponse, UpdateSupplierParams } from '@/tools/sap_s4hana/types' +import { + baseProxyBody, + parseJsonInput, + quoteOdataKey, + SAP_PROXY_URL, + transformSapProxyResponse, +} from '@/tools/sap_s4hana/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateSupplierTool: ToolConfig = { + id: 'sap_s4hana_update_supplier', + name: 'SAP S/4HANA Update Supplier', + description: + 'Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. Note: API_BUSINESS_PARTNER limits A_Supplier PATCH to a small set of modifiable fields (e.g., PostingIsBlocked, PurchasingIsBlocked, PaymentIsBlockedForSupplier, DeletionIndicator, SupplierAccountGroup). Company-code fields like PaymentBlockingReason live on A_SupplierCompany. Most descriptive supplier attributes are read-only here and must be updated via the BusinessPartner entity. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates.', + version: '1.0.0', + params: { + subdomain: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'SAP BTP subaccount subdomain (technical name of your subaccount, not the S/4HANA host)', + }, + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'BTP region (e.g. eu10, us10)', + }, + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client ID from the S/4HANA Communication Arrangement', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'OAuth client secret from the S/4HANA Communication Arrangement', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Deployment type: cloud_public (default), cloud_private, or on_premise', + }, + authType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication type: oauth_client_credentials (default) or basic', + }, + baseUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Base URL of the S/4HANA host (Cloud Private / On-Premise)', + }, + tokenUrl: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'OAuth token URL (Cloud Private / On-Premise + OAuth)', + }, + username: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Username for HTTP Basic auth', + }, + password: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Password for HTTP Basic auth', + }, + supplier: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Supplier key to update (string, up to 10 characters)', + }, + body: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON object with A_Supplier fields to update (e.g., {"PaymentIsBlockedForSupplier":true,"PostingIsBlocked":true})', + }, + ifMatch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'If-Match ETag for optimistic concurrency. Defaults to "*" (unconditional).', + }, + }, + request: { + url: SAP_PROXY_URL, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => { + const payload = parseJsonInput>(params.body, 'body') + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('body must be a JSON object with the fields to update') + } + return { + ...baseProxyBody(params), + service: 'API_BUSINESS_PARTNER', + path: `/A_Supplier(${quoteOdataKey(params.supplier)})`, + method: 'PATCH', + query: { $format: 'json' }, + body: payload, + ifMatch: params.ifMatch || '*', + } + }, + }, + transformResponse: transformSapProxyResponse, + outputs: { + status: { type: 'number', description: 'HTTP status code returned by SAP (204 on success)' }, + data: { + type: 'json', + description: 'Null on 204 success, or updated A_Supplier entity if SAP returns one', + }, + }, +} diff --git a/apps/sim/tools/sap_s4hana/utils.ts b/apps/sim/tools/sap_s4hana/utils.ts new file mode 100644 index 0000000000..203893a5ea --- /dev/null +++ b/apps/sim/tools/sap_s4hana/utils.ts @@ -0,0 +1,90 @@ +import type { SapBaseParams } from '@/tools/sap_s4hana/types' + +export const SAP_PROXY_URL = '/api/tools/sap_s4hana/proxy' + +export function baseProxyBody(params: SapBaseParams) { + const body: Record = {} + if (params.deploymentType) body.deploymentType = params.deploymentType + if (params.authType) body.authType = params.authType + if (params.subdomain) body.subdomain = params.subdomain + if (params.region) body.region = params.region + if (params.baseUrl) body.baseUrl = params.baseUrl + if (params.tokenUrl) body.tokenUrl = params.tokenUrl + if (params.clientId) body.clientId = params.clientId + if (params.clientSecret) body.clientSecret = params.clientSecret + if (params.username) body.username = params.username + if (params.password) body.password = params.password + return body +} + +export function buildOdataQuery(opts: { + filter?: string + top?: number + skip?: number + orderBy?: string + select?: string + expand?: string +}): Record { + const query: Record = { $format: 'json' } + if (opts.filter) query.$filter = opts.filter + if (typeof opts.top === 'number') query.$top = opts.top + if (typeof opts.skip === 'number') query.$skip = opts.skip + if (opts.orderBy) query.$orderby = opts.orderBy + if (opts.select) query.$select = opts.select + if (opts.expand) query.$expand = opts.expand + return query +} + +export function buildEntityQuery(opts: { + select?: string + expand?: string +}): Record { + const query: Record = { $format: 'json' } + if (opts.select) query.$select = opts.select + if (opts.expand) query.$expand = opts.expand + return query +} + +export function parseJsonInput(input: unknown, fieldName: string): T | undefined { + if (input === undefined || input === null || input === '') { + return undefined + } + if (typeof input === 'object') return input as T + if (typeof input !== 'string') { + throw new Error(`Invalid ${fieldName}: expected JSON object or string`) + } + try { + return JSON.parse(input) as T + } catch { + throw new Error(`Invalid ${fieldName}: must be valid JSON`) + } +} + +export function quoteOdataKey(value: string): string { + return `'${String(value).trim().replace(/'/g, "''")}'` +} + +export interface SapProxyToolOutput { + status: number + data: unknown +} + +export async function transformSapProxyResponse( + response: Response +): Promise<{ success: boolean; output: SapProxyToolOutput; error?: string }> { + const data = (await response.json().catch(() => ({}))) as { + success?: boolean + output?: SapProxyToolOutput + error?: string + status?: number + } + + if (!response.ok || data.success === false) { + throw new Error(data.error || `SAP request failed: HTTP ${response.status}`) + } + + return { + success: true, + output: data.output ?? { status: response.status, data: null }, + } +}