Skip to content

Commit c400e59

Browse files
waleedlatif1claude
andauthored
feat(sap_s4hana): add get_material_document and fix supplier invoice key order (#4317)
* fix(sap_s4hana): require non-empty items in create_purchase_order Why: SAP A_PurchaseOrder POST silently fails or returns opaque errors without to_PurchaseOrderItem entries. Block already required this body but the tool marked it optional and didn't validate items presence — mismatched contract with create_sales_order / create_purchase_requisition. Also clarifies the deliveryDocument placeholder to show both outbound and inbound number ranges. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(sap_s4hana): add get_material_document and fix supplier invoice key order Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): align material doc key order in description and require purchase order body type --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2e3de9a commit c400e59

10 files changed

Lines changed: 360 additions & 8 deletions

File tree

apps/docs/content/docs/en/tools/sap_s4hana.mdx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,36 @@ List material document headers (goods movements) from SAP S/4HANA Cloud (API_MAT
844844
| `status` | number | HTTP status code returned by SAP |
845845
| `data` | json | Array of A_MaterialDocumentHeader entities |
846846

847+
### `sap_s4hana_get_material_document`
848+
849+
Retrieve a single material document header by composite key (MaterialDocument + MaterialDocumentYear) from SAP S/4HANA Cloud (API_MATERIAL_DOCUMENT_SRV, A_MaterialDocumentHeader).
850+
851+
#### Input
852+
853+
| Parameter | Type | Required | Description |
854+
| --------- | ---- | -------- | ----------- |
855+
| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) |
856+
| `region` | string | Yes | BTP region \(e.g. eu10, us10\) |
857+
| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement |
858+
| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement |
859+
| `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise |
860+
| `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic |
861+
| `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) |
862+
| `tokenUrl` | string | No | OAuth token URL \(Cloud Private / On-Premise + OAuth\) |
863+
| `username` | string | No | Username for HTTP Basic auth |
864+
| `password` | string | No | Password for HTTP Basic auth |
865+
| `materialDocumentYear` | string | Yes | MaterialDocumentYear \(4-character year, e.g., "2024"\) |
866+
| `materialDocument` | string | Yes | MaterialDocument key \(string, up to 10 characters\) |
867+
| `select` | string | No | Comma-separated fields to return \($select\) |
868+
| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_MaterialDocumentItem"\) |
869+
870+
#### Output
871+
872+
| Parameter | Type | Description |
873+
| --------- | ---- | ----------- |
874+
| `status` | number | HTTP status code returned by SAP |
875+
| `data` | json | A_MaterialDocumentHeader entity |
876+
847877
### `sap_s4hana_list_purchase_requisitions`
848878

849879
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.
@@ -1047,7 +1077,7 @@ Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_P
10471077
| `purchasingOrganization` | string | Yes | PurchasingOrganization \(4 chars\) |
10481078
| `purchasingGroup` | string | Yes | PurchasingGroup \(3 chars\) |
10491079
| `supplier` | string | Yes | Supplier business partner key \(up to 10 chars\) |
1050-
| `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"\}\]\}\). |
1080+
| `body` | json | Yes | A_PurchaseOrder body containing to_PurchaseOrderItem deep-insert items \(required by SAP\) plus any additional header fields, e.g., \{"to_PurchaseOrderItem":\[\{"PurchaseOrderItem":"10","Material":"TG11","OrderQuantity":"5","Plant":"1010","PurchaseOrderQuantityUnit":"PC","NetPriceAmount":"100.00","DocumentCurrency":"USD"\}\]\}. |
10511081

10521082
#### Output
10531083

apps/docs/content/docs/en/tools/slack.mdx

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,139 @@ Create a canvas pinned to a Slack channel as its resource hub
925925
| --------- | ---- | ----------- |
926926
| `canvas_id` | string | ID of the created channel canvas |
927927

928+
### `slack_get_canvas`
929+
930+
Get Slack canvas file metadata by canvas ID
931+
932+
#### Input
933+
934+
| Parameter | Type | Required | Description |
935+
| --------- | ---- | -------- | ----------- |
936+
| `authMethod` | string | No | Authentication method: oauth or bot_token |
937+
| `botToken` | string | No | Bot token for Custom Bot |
938+
| `canvasId` | string | Yes | Canvas file ID to retrieve \(e.g., F1234ABCD\) |
939+
940+
#### Output
941+
942+
| Parameter | Type | Description |
943+
| --------- | ---- | ----------- |
944+
| `canvas` | object | Canvas file information returned by Slack |
945+
|`id` | string | Unique canvas file identifier |
946+
|`created` | number | Unix timestamp when the canvas was created |
947+
|`timestamp` | number | Unix timestamp associated with the canvas |
948+
|`name` | string | Canvas file name |
949+
|`title` | string | Canvas title |
950+
|`mimetype` | string | MIME type of the canvas file |
951+
|`filetype` | string | Slack file type for the canvas |
952+
|`pretty_type` | string | Human-readable file type |
953+
|`user` | string | User ID of the canvas creator |
954+
|`editable` | boolean | Whether the canvas file is editable |
955+
|`size` | number | Canvas file size in bytes |
956+
|`mode` | string | File mode |
957+
|`is_external` | boolean | Whether the canvas is externally hosted |
958+
|`is_public` | boolean | Whether the canvas is public |
959+
|`url_private` | string | Private URL for the canvas file |
960+
|`url_private_download` | string | Private download URL for the canvas file |
961+
|`permalink` | string | Permanent URL for the canvas |
962+
|`channels` | array | Public channel IDs where the canvas appears |
963+
|`groups` | array | Private channel IDs where the canvas appears |
964+
|`ims` | array | Direct message IDs where the canvas appears |
965+
|`canvas_readtime` | number | Approximate read time for canvas content |
966+
|`is_channel_space` | boolean | Whether this canvas is linked to a channel |
967+
|`linked_channel_id` | string | Channel ID linked to this canvas |
968+
|`canvas_creator_id` | string | User ID of the canvas creator |
969+
970+
### `slack_list_canvases`
971+
972+
List Slack canvases available to the authenticated user or bot
973+
974+
#### Input
975+
976+
| Parameter | Type | Required | Description |
977+
| --------- | ---- | -------- | ----------- |
978+
| `authMethod` | string | No | Authentication method: oauth or bot_token |
979+
| `botToken` | string | No | Bot token for Custom Bot |
980+
| `channel` | string | No | Filter canvases appearing in a specific channel ID |
981+
| `count` | number | No | Number of canvases to return per page |
982+
| `page` | number | No | Page number to return |
983+
| `user` | string | No | Filter canvases created by a single user ID |
984+
| `tsFrom` | string | No | Filter canvases created after this Unix timestamp |
985+
| `tsTo` | string | No | Filter canvases created before this Unix timestamp |
986+
| `teamId` | string | No | Encoded team ID, required when using an org-level token |
987+
988+
#### Output
989+
990+
| Parameter | Type | Description |
991+
| --------- | ---- | ----------- |
992+
| `canvases` | array | Canvas file objects returned by Slack |
993+
|`id` | string | Unique canvas file identifier |
994+
|`created` | number | Unix timestamp when the canvas was created |
995+
|`timestamp` | number | Unix timestamp associated with the canvas |
996+
|`name` | string | Canvas file name |
997+
|`title` | string | Canvas title |
998+
|`mimetype` | string | MIME type of the canvas file |
999+
|`filetype` | string | Slack file type for the canvas |
1000+
|`pretty_type` | string | Human-readable file type |
1001+
|`user` | string | User ID of the canvas creator |
1002+
|`editable` | boolean | Whether the canvas file is editable |
1003+
|`size` | number | Canvas file size in bytes |
1004+
|`mode` | string | File mode |
1005+
|`is_external` | boolean | Whether the canvas is externally hosted |
1006+
|`is_public` | boolean | Whether the canvas is public |
1007+
|`url_private` | string | Private URL for the canvas file |
1008+
|`url_private_download` | string | Private download URL for the canvas file |
1009+
|`permalink` | string | Permanent URL for the canvas |
1010+
|`channels` | array | Public channel IDs where the canvas appears |
1011+
|`groups` | array | Private channel IDs where the canvas appears |
1012+
|`ims` | array | Direct message IDs where the canvas appears |
1013+
|`canvas_readtime` | number | Approximate read time for canvas content |
1014+
|`is_channel_space` | boolean | Whether this canvas is linked to a channel |
1015+
|`linked_channel_id` | string | Channel ID linked to this canvas |
1016+
|`canvas_creator_id` | string | User ID of the canvas creator |
1017+
| `paging` | object | Pagination information from Slack |
1018+
|`count` | number | Number of items requested per page |
1019+
|`total` | number | Total number of matching files |
1020+
|`page` | number | Current page number |
1021+
|`pages` | number | Total number of pages |
1022+
1023+
### `slack_lookup_canvas_sections`
1024+
1025+
Find Slack canvas section IDs matching criteria for later edits
1026+
1027+
#### Input
1028+
1029+
| Parameter | Type | Required | Description |
1030+
| --------- | ---- | -------- | ----------- |
1031+
| `authMethod` | string | No | Authentication method: oauth or bot_token |
1032+
| `botToken` | string | No | Bot token for Custom Bot |
1033+
| `canvasId` | string | Yes | Canvas ID to search \(e.g., F1234ABCD\) |
1034+
| `criteria` | json | Yes | Section lookup criteria, such as \{"section_types":\["h1"\],"contains_text":"Roadmap"\} |
1035+
1036+
#### Output
1037+
1038+
| Parameter | Type | Description |
1039+
| --------- | ---- | ----------- |
1040+
| `sections` | array | Canvas sections matching the lookup criteria |
1041+
|`id` | string | Canvas section identifier |
1042+
1043+
### `slack_delete_canvas`
1044+
1045+
Delete a Slack canvas by its canvas ID
1046+
1047+
#### Input
1048+
1049+
| Parameter | Type | Required | Description |
1050+
| --------- | ---- | -------- | ----------- |
1051+
| `authMethod` | string | No | Authentication method: oauth or bot_token |
1052+
| `botToken` | string | No | Bot token for Custom Bot |
1053+
| `canvasId` | string | Yes | Canvas ID to delete \(e.g., F1234ABCD\) |
1054+
1055+
#### Output
1056+
1057+
| Parameter | Type | Description |
1058+
| --------- | ---- | ----------- |
1059+
| `ok` | boolean | Whether Slack deleted the canvas successfully |
1060+
9281061
### `slack_create_conversation`
9291062

9301063
Create a new public or private channel in a Slack workspace.

apps/sim/app/(landing)/integrations/data/integrations.json

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11493,6 +11493,10 @@
1149311493
"name": "List Material Documents",
1149411494
"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."
1149511495
},
11496+
{
11497+
"name": "Get Material Document",
11498+
"description": "Retrieve a single material document header by composite key (MaterialDocument + MaterialDocumentYear) from SAP S/4HANA Cloud (API_MATERIAL_DOCUMENT_SRV, A_MaterialDocumentHeader)."
11499+
},
1149611500
{
1149711501
"name": "List Purchase Requisitions",
1149811502
"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."
@@ -11538,7 +11542,7 @@
1153811542
"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."
1153911543
}
1154011544
],
11541-
"operationCount": 37,
11545+
"operationCount": 38,
1154211546
"triggers": [],
1154311547
"triggerCount": 0,
1154411548
"authType": "none",
@@ -12150,6 +12154,22 @@
1215012154
"name": "Create Channel Canvas",
1215112155
"description": "Create a canvas pinned to a Slack channel as its resource hub"
1215212156
},
12157+
{
12158+
"name": "Get Canvas Info",
12159+
"description": "Get Slack canvas file metadata by canvas ID"
12160+
},
12161+
{
12162+
"name": "List Canvases",
12163+
"description": "List Slack canvases available to the authenticated user or bot"
12164+
},
12165+
{
12166+
"name": "Lookup Canvas Sections",
12167+
"description": "Find Slack canvas section IDs matching criteria for later edits"
12168+
},
12169+
{
12170+
"name": "Delete Canvas",
12171+
"description": "Delete a Slack canvas by its canvas ID"
12172+
},
1215312173
{
1215412174
"name": "Create Conversation",
1215512175
"description": "Create a new public or private channel in a Slack workspace."
@@ -12175,7 +12195,7 @@
1217512195
"description": "Publish a static view to a user"
1217612196
}
1217712197
],
12178-
"operationCount": 25,
12198+
"operationCount": 29,
1217912199
"triggers": [
1218012200
{
1218112201
"id": "slack_webhook",

apps/sim/blocks/blocks/sap_s4hana.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
4848
{ label: 'Update Product', id: 'sap_s4hana_update_product' },
4949
{ label: 'List Material Stock', id: 'sap_s4hana_list_material_stock' },
5050
{ label: 'List Material Documents', id: 'sap_s4hana_list_material_documents' },
51+
{ label: 'Get Material Document', id: 'sap_s4hana_get_material_document' },
5152
{ label: 'List Purchase Requisitions', id: 'sap_s4hana_list_purchase_requisitions' },
5253
{ label: 'Get Purchase Requisition', id: 'sap_s4hana_get_purchase_requisition' },
5354
{ label: 'Create Purchase Requisition', id: 'sap_s4hana_create_purchase_requisition' },
@@ -189,6 +190,7 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
189190
'sap_s4hana_get_product',
190191
'sap_s4hana_list_material_stock',
191192
'sap_s4hana_list_material_documents',
193+
'sap_s4hana_get_material_document',
192194
'sap_s4hana_list_purchase_requisitions',
193195
'sap_s4hana_get_purchase_requisition',
194196
'sap_s4hana_list_purchase_orders',
@@ -225,6 +227,7 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
225227
'sap_s4hana_get_product',
226228
'sap_s4hana_list_material_stock',
227229
'sap_s4hana_list_material_documents',
230+
'sap_s4hana_get_material_document',
228231
'sap_s4hana_list_purchase_requisitions',
229232
'sap_s4hana_get_purchase_requisition',
230233
'sap_s4hana_list_purchase_orders',
@@ -417,7 +420,7 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
417420
id: 'deliveryDocument',
418421
title: 'DeliveryDocument',
419422
type: 'short-input',
420-
placeholder: '80000000',
423+
placeholder: 'e.g., 80000000 (outbound) or 180000000 (inbound)',
421424
condition: {
422425
field: 'operation',
423426
value: ['sap_s4hana_get_outbound_delivery', 'sap_s4hana_get_inbound_delivery'],
@@ -556,6 +559,24 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
556559
required: true,
557560
},
558561

562+
// Material Document: get
563+
{
564+
id: 'materialDocumentYear',
565+
title: 'MaterialDocumentYear',
566+
type: 'short-input',
567+
placeholder: '2024',
568+
condition: { field: 'operation', value: 'sap_s4hana_get_material_document' },
569+
required: true,
570+
},
571+
{
572+
id: 'materialDocument',
573+
title: 'MaterialDocument',
574+
type: 'short-input',
575+
placeholder: '4900000000',
576+
condition: { field: 'operation', value: 'sap_s4hana_get_material_document' },
577+
required: true,
578+
},
579+
559580
// Supplier Invoice: get
560581
{
561582
id: 'supplierInvoice',
@@ -844,6 +865,7 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
844865
'sap_s4hana_update_product',
845866
'sap_s4hana_list_material_stock',
846867
'sap_s4hana_list_material_documents',
868+
'sap_s4hana_get_material_document',
847869
'sap_s4hana_list_purchase_requisitions',
848870
'sap_s4hana_get_purchase_requisition',
849871
'sap_s4hana_create_purchase_requisition',
@@ -991,6 +1013,13 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
9911013
return { ...auth, ...listFields }
9921014
case 'sap_s4hana_list_material_documents':
9931015
return { ...auth, ...listFields }
1016+
case 'sap_s4hana_get_material_document':
1017+
return {
1018+
...auth,
1019+
...entityFields,
1020+
materialDocumentYear: params.materialDocumentYear,
1021+
materialDocument: params.materialDocument,
1022+
}
9941023
case 'sap_s4hana_list_purchase_requisitions':
9951024
return { ...auth, ...listFields }
9961025
case 'sap_s4hana_get_purchase_requisition':
@@ -1124,6 +1153,8 @@ export const SapS4HanaBlock: BlockConfig<SapProxyResponse> = {
11241153
purchaseOrderBody: { type: 'json', description: 'Items and additional A_PurchaseOrder fields' },
11251154
supplierInvoice: { type: 'string', description: 'SupplierInvoice key' },
11261155
fiscalYear: { type: 'string', description: 'FiscalYear (4-digit year)' },
1156+
materialDocumentYear: { type: 'string', description: 'MaterialDocumentYear (4-digit year)' },
1157+
materialDocument: { type: 'string', description: 'MaterialDocument key' },
11271158
odataService: { type: 'string', description: 'OData service name' },
11281159
odataPath: { type: 'string', description: 'OData entity path' },
11291160
odataMethod: { type: 'string', description: 'HTTP method for OData call' },

apps/sim/tools/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,7 @@ import {
22582258
getBusinessPartnerTool as sapS4HanaGetBusinessPartnerTool,
22592259
getCustomerTool as sapS4HanaGetCustomerTool,
22602260
getInboundDeliveryTool as sapS4HanaGetInboundDeliveryTool,
2261+
getMaterialDocumentTool as sapS4HanaGetMaterialDocumentTool,
22612262
getOutboundDeliveryTool as sapS4HanaGetOutboundDeliveryTool,
22622263
getProductTool as sapS4HanaGetProductTool,
22632264
getPurchaseOrderTool as sapS4HanaGetPurchaseOrderTool,
@@ -5334,6 +5335,7 @@ export const tools: Record<string, ToolConfig> = {
53345335
sap_s4hana_get_business_partner: sapS4HanaGetBusinessPartnerTool,
53355336
sap_s4hana_get_customer: sapS4HanaGetCustomerTool,
53365337
sap_s4hana_get_inbound_delivery: sapS4HanaGetInboundDeliveryTool,
5338+
sap_s4hana_get_material_document: sapS4HanaGetMaterialDocumentTool,
53375339
sap_s4hana_get_outbound_delivery: sapS4HanaGetOutboundDeliveryTool,
53385340
sap_s4hana_get_product: sapS4HanaGetProductTool,
53395341
sap_s4hana_get_purchase_order: sapS4HanaGetPurchaseOrderTool,

apps/sim/tools/sap_s4hana/create_purchase_order.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ export const createPurchaseOrderTool: ToolConfig<CreatePurchaseOrderParams, SapP
107107
},
108108
body: {
109109
type: 'json',
110-
required: false,
110+
required: true,
111111
visibility: 'user-or-llm',
112112
description:
113-
'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"}]}).',
113+
'A_PurchaseOrder body containing to_PurchaseOrderItem deep-insert items (required by SAP) plus any additional header fields, e.g., {"to_PurchaseOrderItem":[{"PurchaseOrderItem":"10","Material":"TG11","OrderQuantity":"5","Plant":"1010","PurchaseOrderQuantityUnit":"PC","NetPriceAmount":"100.00","DocumentCurrency":"USD"}]}.',
114114
},
115115
},
116116
request: {
@@ -119,6 +119,12 @@ export const createPurchaseOrderTool: ToolConfig<CreatePurchaseOrderParams, SapP
119119
headers: () => ({ 'Content-Type': 'application/json' }),
120120
body: (params) => {
121121
const extra = parseJsonInput<Record<string, unknown>>(params.body, 'body') ?? {}
122+
const items = Array.isArray(extra.to_PurchaseOrderItem) ? extra.to_PurchaseOrderItem : null
123+
if (!items || items.length === 0) {
124+
throw new Error(
125+
'body must include a non-empty "to_PurchaseOrderItem" array of purchase order line items'
126+
)
127+
}
122128
const payload: Record<string, unknown> = {
123129
...extra,
124130
PurchaseOrderType: params.purchaseOrderType,

0 commit comments

Comments
 (0)