@arbium/enterprise-agent-connect 0.1.1 → 0.1.2-dev.cba5f68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -12
- package/dist/connectors/dell-techdirect/index.d.ts +8 -0
- package/dist/connectors/dell-techdirect/tools.d.ts +2 -0
- package/dist/connectors/dell-techdirect/vendor.d.ts +6 -0
- package/dist/connectors/dynamics-365/connector.json +228 -0
- package/dist/connectors/dynamics-365/index.d.ts +8 -0
- package/dist/connectors/dynamics-365/index.js +10 -0
- package/dist/connectors/dynamics-365/index.js.map +1 -0
- package/dist/connectors/dynamics-365/tools.d.ts +35 -0
- package/dist/connectors/dynamics-365/tools.js +806 -0
- package/dist/connectors/dynamics-365/tools.js.map +1 -0
- package/dist/connectors/dynamics-365/vendor.d.ts +9 -0
- package/dist/connectors/dynamics-365/vendor.js +110 -0
- package/dist/connectors/dynamics-365/vendor.js.map +1 -0
- package/dist/connectors/entra/index.d.ts +2 -0
- package/dist/connectors/entra/tools.d.ts +14 -0
- package/dist/connectors/fedex/index.d.ts +8 -0
- package/dist/connectors/fedex/tools.d.ts +3 -0
- package/dist/connectors/fedex/vendor.d.ts +7 -0
- package/dist/connectors/gong/connector.json +127 -0
- package/dist/connectors/gong/index.d.ts +8 -0
- package/dist/connectors/gong/index.js +7 -0
- package/dist/connectors/gong/index.js.map +1 -0
- package/dist/connectors/gong/tools.d.ts +8 -0
- package/dist/connectors/gong/tools.js +424 -0
- package/dist/connectors/gong/tools.js.map +1 -0
- package/dist/connectors/gong/vendor.d.ts +4 -0
- package/dist/connectors/gong/vendor.js +89 -0
- package/dist/connectors/gong/vendor.js.map +1 -0
- package/dist/connectors/http/index.d.ts +34 -0
- package/dist/connectors/http/index.js +154 -64
- package/dist/connectors/http/index.js.map +1 -1
- package/dist/connectors/intune/index.d.ts +2 -0
- package/dist/connectors/intune/tools.d.ts +12 -0
- package/dist/connectors/jamf/connector.json +182 -0
- package/dist/connectors/jamf/index.d.ts +8 -0
- package/dist/connectors/jamf/index.js +7 -0
- package/dist/connectors/jamf/index.js.map +1 -0
- package/dist/connectors/jamf/tools.d.ts +7 -0
- package/dist/connectors/jamf/tools.js +659 -0
- package/dist/connectors/jamf/tools.js.map +1 -0
- package/dist/connectors/jamf/vendor.d.ts +5 -0
- package/dist/connectors/jamf/vendor.js +70 -0
- package/dist/connectors/jamf/vendor.js.map +1 -0
- package/dist/connectors/jira/connector.json +144 -1
- package/dist/connectors/jira/index.d.ts +8 -0
- package/dist/connectors/jira/jql.d.ts +4 -0
- package/dist/connectors/jira/tools.d.ts +3 -0
- package/dist/connectors/jira/vendor.d.ts +3 -0
- package/dist/connectors/jira/vendor.js +8 -2
- package/dist/connectors/jira/vendor.js.map +1 -1
- package/dist/connectors/microsoft/client.d.ts +66 -0
- package/dist/connectors/microsoft/client.js +26 -8
- package/dist/connectors/microsoft/client.js.map +1 -1
- package/dist/connectors/netsuite/connector.json +317 -0
- package/dist/connectors/netsuite/index.d.ts +8 -0
- package/dist/connectors/netsuite/index.js +7 -0
- package/dist/connectors/netsuite/index.js.map +1 -0
- package/dist/connectors/netsuite/tools.d.ts +24 -0
- package/dist/connectors/netsuite/tools.js +447 -0
- package/dist/connectors/netsuite/tools.js.map +1 -0
- package/dist/connectors/netsuite/vendor.d.ts +26 -0
- package/dist/connectors/netsuite/vendor.js +247 -0
- package/dist/connectors/netsuite/vendor.js.map +1 -0
- package/dist/connectors/odata/index.d.ts +33 -0
- package/dist/connectors/odata/index.js +460 -0
- package/dist/connectors/odata/index.js.map +1 -0
- package/dist/connectors/okta/connector.json +194 -0
- package/dist/connectors/okta/index.d.ts +8 -0
- package/dist/connectors/okta/index.js +7 -0
- package/dist/connectors/okta/index.js.map +1 -0
- package/dist/connectors/okta/tools.d.ts +6 -0
- package/dist/connectors/okta/tools.js +429 -0
- package/dist/connectors/okta/tools.js.map +1 -0
- package/dist/connectors/okta/vendor.d.ts +14 -0
- package/dist/connectors/okta/vendor.js +204 -0
- package/dist/connectors/okta/vendor.js.map +1 -0
- package/dist/connectors/outlook-calendar/index.d.ts +2 -0
- package/dist/connectors/outlook-calendar/tools.d.ts +65 -0
- package/dist/connectors/outlook-mail/index.d.ts +2 -0
- package/dist/connectors/outlook-mail/tools.d.ts +8 -0
- package/dist/connectors/salesforce/index.d.ts +8 -0
- package/dist/connectors/salesforce/soql.d.ts +3 -0
- package/dist/connectors/salesforce/tools.d.ts +9 -0
- package/dist/connectors/salesforce/vendor.d.ts +3 -0
- package/dist/connectors/salesforce/vendor.js +9 -3
- package/dist/connectors/salesforce/vendor.js.map +1 -1
- package/dist/connectors/sap-s4hana/connector.json +264 -0
- package/dist/connectors/sap-s4hana/index.d.ts +8 -0
- package/dist/connectors/sap-s4hana/index.js +7 -0
- package/dist/connectors/sap-s4hana/index.js.map +1 -0
- package/dist/connectors/sap-s4hana/tools.d.ts +4 -0
- package/dist/connectors/sap-s4hana/tools.js +761 -0
- package/dist/connectors/sap-s4hana/tools.js.map +1 -0
- package/dist/connectors/sap-s4hana/vendor.d.ts +5 -0
- package/dist/connectors/sap-s4hana/vendor.js +176 -0
- package/dist/connectors/sap-s4hana/vendor.js.map +1 -0
- package/dist/connectors/servicenow/connector.json +228 -0
- package/dist/connectors/servicenow/index.d.ts +8 -0
- package/dist/connectors/servicenow/index.js +7 -0
- package/dist/connectors/servicenow/index.js.map +1 -0
- package/dist/connectors/servicenow/tools.d.ts +4 -0
- package/dist/connectors/servicenow/tools.js +884 -0
- package/dist/connectors/servicenow/tools.js.map +1 -0
- package/dist/connectors/servicenow/vendor.d.ts +3 -0
- package/dist/connectors/servicenow/vendor.js +106 -0
- package/dist/connectors/servicenow/vendor.js.map +1 -0
- package/dist/connectors/sharepoint-files/excel.d.ts +16 -0
- package/dist/connectors/sharepoint-files/index.d.ts +8 -0
- package/dist/connectors/sharepoint-files/index.js.map +1 -1
- package/dist/connectors/sharepoint-files/tools.d.ts +52 -0
- package/dist/connectors/sharepoint-files/vendor.d.ts +11 -0
- package/dist/connectors/slack/connector.json +148 -0
- package/dist/connectors/slack/index.d.ts +8 -0
- package/dist/connectors/slack/index.js +7 -0
- package/dist/connectors/slack/index.js.map +1 -0
- package/dist/connectors/slack/tools.d.ts +3 -0
- package/dist/connectors/slack/tools.js +355 -0
- package/dist/connectors/slack/tools.js.map +1 -0
- package/dist/connectors/slack/vendor.d.ts +17 -0
- package/dist/connectors/slack/vendor.js +189 -0
- package/dist/connectors/slack/vendor.js.map +1 -0
- package/dist/connectors/soap/index.d.ts +16 -0
- package/dist/connectors/soap/index.js +248 -0
- package/dist/connectors/soap/index.js.map +1 -0
- package/dist/connectors/soap/xml.d.ts +12 -0
- package/dist/connectors/soap/xml.js +126 -0
- package/dist/connectors/soap/xml.js.map +1 -0
- package/dist/connectors/sql/engines.d.ts +58 -0
- package/dist/connectors/sql/engines.js +292 -0
- package/dist/connectors/sql/engines.js.map +1 -0
- package/dist/connectors/sql/index.d.ts +16 -0
- package/dist/connectors/sql/index.js +214 -0
- package/dist/connectors/sql/index.js.map +1 -0
- package/dist/connectors/teams/index.d.ts +2 -0
- package/dist/connectors/teams/tools.d.ts +7 -0
- package/dist/connectors/teams/tools.js +3 -2
- package/dist/connectors/teams/tools.js.map +1 -1
- package/dist/connectors/workday/connector.json +188 -0
- package/dist/connectors/workday/index.d.ts +8 -0
- package/dist/connectors/workday/index.js +7 -0
- package/dist/connectors/workday/index.js.map +1 -0
- package/dist/connectors/workday/tools.d.ts +33 -0
- package/dist/connectors/workday/tools.js +410 -0
- package/dist/connectors/workday/tools.js.map +1 -0
- package/dist/connectors/workday/vendor.d.ts +18 -0
- package/dist/connectors/workday/vendor.js +199 -0
- package/dist/connectors/workday/vendor.js.map +1 -0
- package/dist/packages/connector-kit/src/branding.d.ts +57 -0
- package/dist/packages/connector-kit/src/certificate.d.ts +19 -0
- package/dist/packages/connector-kit/src/common.d.ts +36 -0
- package/dist/packages/connector-kit/src/common.js +4 -1
- package/dist/packages/connector-kit/src/common.js.map +1 -1
- package/dist/packages/connector-kit/src/connector.d.ts +1356 -0
- package/dist/packages/connector-kit/src/connector.js +632 -53
- package/dist/packages/connector-kit/src/connector.js.map +1 -1
- package/dist/packages/connector-kit/src/fetch.d.ts +6 -0
- package/dist/packages/connector-kit/src/fetch.js +44 -0
- package/dist/packages/connector-kit/src/fetch.js.map +1 -0
- package/dist/packages/connector-kit/src/index.d.ts +5 -0
- package/dist/packages/connector-kit/src/index.js +6 -0
- package/dist/packages/connector-kit/src/index.js.map +1 -0
- package/dist/packages/connector-kit/src/oauth.d.ts +15 -0
- package/dist/packages/connector-kit/src/oauth.js +25 -6
- package/dist/packages/connector-kit/src/oauth.js.map +1 -1
- package/dist/packages/connector-kit/src/types.d.ts +87 -0
- package/dist/packages/connector-kit/src/types.js.map +1 -1
- package/dist/packages/server/migrations/0007_app_events.sql +110 -0
- package/dist/packages/server/migrations/rollback/to-legacy.sql +3 -0
- package/dist/packages/server/src/api.d.ts +3730 -0
- package/dist/packages/server/src/api.js +137 -1
- package/dist/packages/server/src/api.js.map +1 -1
- package/dist/packages/server/src/app.d.ts +23 -0
- package/dist/packages/server/src/app.js +24 -1
- package/dist/packages/server/src/app.js.map +1 -1
- package/dist/packages/server/src/builtin.d.ts +5 -0
- package/dist/packages/server/src/builtin.js +18 -0
- package/dist/packages/server/src/builtin.js.map +1 -1
- package/dist/packages/server/src/consent.d.ts +27 -0
- package/dist/packages/server/src/events/admin.d.ts +156 -0
- package/dist/packages/server/src/events/admin.js +255 -0
- package/dist/packages/server/src/events/admin.js.map +1 -0
- package/dist/packages/server/src/events/deliver.d.ts +43 -0
- package/dist/packages/server/src/events/deliver.js +129 -0
- package/dist/packages/server/src/events/deliver.js.map +1 -0
- package/dist/packages/server/src/events/runtime.d.ts +28 -0
- package/dist/packages/server/src/events/runtime.js +397 -0
- package/dist/packages/server/src/events/runtime.js.map +1 -0
- package/dist/packages/server/src/events/store.d.ts +172 -0
- package/dist/packages/server/src/events/store.js +275 -0
- package/dist/packages/server/src/events/store.js.map +1 -0
- package/dist/packages/server/src/index.d.ts +40 -0
- package/dist/packages/server/src/index.js +68 -11
- package/dist/packages/server/src/index.js.map +1 -1
- package/dist/packages/server/src/migrate.d.ts +11 -0
- package/dist/packages/server/src/openapi.d.ts +4 -0
- package/dist/packages/server/src/openapi.js +2 -2
- package/dist/packages/server/src/openapi.js.map +1 -1
- package/dist/packages/server/src/queue.d.ts +10 -0
- package/dist/packages/server/src/queue.js +45 -0
- package/dist/packages/server/src/queue.js.map +1 -0
- package/dist/packages/server/src/secrets.d.ts +19 -0
- package/dist/packages/server/src/server.d.ts +1 -0
- package/dist/packages/server/src/server.js +14 -1
- package/dist/packages/server/src/server.js.map +1 -1
- package/dist/packages/server/src/service.d.ts +767 -0
- package/dist/packages/server/src/service.js +19 -3
- package/dist/packages/server/src/service.js.map +1 -1
- package/dist/packages/server/src/store.d.ts +157 -0
- package/dist/packages/server/src/store.js +4 -0
- package/dist/packages/server/src/store.js.map +1 -1
- package/dist/packages/server/src/teams/activity.d.ts +123 -0
- package/dist/packages/server/src/teams/activity.js +6 -5
- package/dist/packages/server/src/teams/activity.js.map +1 -1
- package/dist/packages/server/src/teams/admin.d.ts +335 -0
- package/dist/packages/server/src/teams/admin.js +1 -1
- package/dist/packages/server/src/teams/admin.js.map +1 -1
- package/dist/packages/server/src/teams/approval.d.ts +57 -0
- package/dist/packages/server/src/teams/bot.d.ts +25 -0
- package/dist/packages/server/src/teams/claude.d.ts +46 -0
- package/dist/packages/server/src/teams/jwt.d.ts +17 -0
- package/dist/packages/server/src/teams/package.d.ts +79 -0
- package/dist/packages/server/src/teams/provision.d.ts +84 -0
- package/dist/packages/server/src/teams/runner.d.ts +80 -0
- package/dist/packages/server/src/teams/runner.js +50 -6
- package/dist/packages/server/src/teams/runner.js.map +1 -1
- package/dist/packages/server/src/teams/runtime.d.ts +91 -0
- package/dist/packages/server/src/teams/runtime.js +25 -47
- package/dist/packages/server/src/teams/runtime.js.map +1 -1
- package/dist/packages/server/src/teams/store.d.ts +233 -0
- package/package.json +28 -2
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ODATA } from "./vendor.js";
|
|
3
|
+
import { audited, Denied, json, listLimit, VendorFailed, } from "../../packages/connector-kit/src/common.js";
|
|
4
|
+
import { ConnectError } from "../../packages/connector-kit/src/types.js";
|
|
5
|
+
const BP = `${ODATA}/API_BUSINESS_PARTNER`;
|
|
6
|
+
const SALES = `${ODATA}/API_SALES_ORDER_SRV`;
|
|
7
|
+
const PURCHASING = `${ODATA}/API_PURCHASEORDER_PROCESS_SRV`;
|
|
8
|
+
const STOCK = `${ODATA}/API_MATERIAL_STOCK_SRV`;
|
|
9
|
+
const PRODUCT = `${ODATA}/API_PRODUCT_SRV`;
|
|
10
|
+
const INVOICES = `${ODATA}/API_SUPPLIERINVOICE_PROCESS_SRV`;
|
|
11
|
+
const ITEMS = `${ODATA}/API_OPLACCTGDOCITEMCUBE_SRV`;
|
|
12
|
+
const ORG = /^[A-Za-z0-9]{1,4}$/;
|
|
13
|
+
const DOC = /^[A-Za-z0-9]{1,10}$/;
|
|
14
|
+
const MATERIAL = /^[A-Za-z0-9][A-Za-z0-9 ._/-]{0,39}$/;
|
|
15
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
16
|
+
async function ok(ctx, mark, req) {
|
|
17
|
+
let res;
|
|
18
|
+
try {
|
|
19
|
+
res = await ctx.call(req);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error instanceof ConnectError)
|
|
23
|
+
throw new VendorFailed(502, error.message);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
mark(res.status);
|
|
27
|
+
if (res.status < 200 || res.status >= 300) {
|
|
28
|
+
const detail = sapError(res.data);
|
|
29
|
+
throw new VendorFailed(res.status, `SAP answered ${res.status}${detail ? `: ${detail}` : ""}`);
|
|
30
|
+
}
|
|
31
|
+
return res.data;
|
|
32
|
+
}
|
|
33
|
+
// OData v2 errors: { error: { code, message: { lang, value } } }. Anything else (an HTML logon
|
|
34
|
+
// page, say) is not shown.
|
|
35
|
+
export function sapError(data) {
|
|
36
|
+
const e = data?.error;
|
|
37
|
+
const text = e?.message?.value;
|
|
38
|
+
return typeof text === "string" ? text.slice(0, 300) : "";
|
|
39
|
+
}
|
|
40
|
+
// OData v2 JSON: a collection is d.results, an entity d, an expanded list its own results.
|
|
41
|
+
const results = (v) => (Array.isArray(v?.results) ? v.results : []);
|
|
42
|
+
// OData v2 dates are "/Date(ms)/" in UTC.
|
|
43
|
+
export function day(v) {
|
|
44
|
+
const m = typeof v === "string" ? /^\/Date\((-?\d+)/.exec(v) : null;
|
|
45
|
+
return m ? new Date(Number(m[1])).toISOString().slice(0, 10) : null;
|
|
46
|
+
}
|
|
47
|
+
const sapDate = (d) => `/Date(${Date.parse(`${d}T00:00:00Z`)})/`;
|
|
48
|
+
const num = (v) => (v === null || v === undefined || v === "" ? null : Number(v));
|
|
49
|
+
const text = (v) => (typeof v === "string" && v !== "" ? v : null);
|
|
50
|
+
const lit = (v) => `'${v.replaceAll("'", "''")}'`;
|
|
51
|
+
const anyOf = (field, values) => `(${values.map((v) => `${field} eq ${lit(v)}`).join(" or ")})`;
|
|
52
|
+
const since = (field, d) => `${field} ge datetime'${d}T00:00:00'`;
|
|
53
|
+
const until = (field, d) => `${field} le datetime'${d}T23:59:59'`;
|
|
54
|
+
function checkDay(d) {
|
|
55
|
+
if (d && (!DAY.test(d) || Number.isNaN(Date.parse(`${d}T00:00:00Z`))))
|
|
56
|
+
throw new Denied(`${d} is not a date (YYYY-MM-DD)`);
|
|
57
|
+
}
|
|
58
|
+
// The values a read may cover for one limit: the one asked for (which must be allowed), else
|
|
59
|
+
// the agent's whole list, else null (no limit and none asked).
|
|
60
|
+
function scope(ctx, limit, asked, what) {
|
|
61
|
+
const allowed = listLimit(ctx.limits, limit);
|
|
62
|
+
if (asked !== undefined) {
|
|
63
|
+
if (allowed && !allowed.includes(asked))
|
|
64
|
+
throw new Denied(`This agent is not allowed to use ${what} ${asked}`);
|
|
65
|
+
return [asked];
|
|
66
|
+
}
|
|
67
|
+
return allowed;
|
|
68
|
+
}
|
|
69
|
+
const allowedValue = (ctx, limit, value) => {
|
|
70
|
+
const allowed = listLimit(ctx.limits, limit);
|
|
71
|
+
return !allowed || (typeof value === "string" && allowed.includes(value));
|
|
72
|
+
};
|
|
73
|
+
function collection(path, o) {
|
|
74
|
+
const q = [
|
|
75
|
+
`$select=${o.select.join(",")}`,
|
|
76
|
+
...(o.expand ? [`$expand=${o.expand}`] : []),
|
|
77
|
+
...(o.filters.length ? [`$filter=${encodeURIComponent(o.filters.join(" and "))}`] : []),
|
|
78
|
+
...(o.orderby ? [`$orderby=${encodeURIComponent(o.orderby)}`] : []),
|
|
79
|
+
`$top=${o.top + 1}`,
|
|
80
|
+
`$skip=${o.skip}`,
|
|
81
|
+
];
|
|
82
|
+
return `${path}?${q.join("&")}`;
|
|
83
|
+
}
|
|
84
|
+
const paging = {
|
|
85
|
+
cursor: z
|
|
86
|
+
.string()
|
|
87
|
+
.regex(/^\d{1,6}$/)
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("The `next` of an earlier answer."),
|
|
90
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
91
|
+
};
|
|
92
|
+
// Asks one more row than shown, so `next` is only given when there is more.
|
|
93
|
+
async function page(ctx, mark, path, o, cursor, limit) {
|
|
94
|
+
const skip = Number(cursor ?? 0);
|
|
95
|
+
const rows = results((await ok(ctx, mark, { method: "GET", path: collection(path, { ...o, skip, top: limit }) })).d);
|
|
96
|
+
return { rows: rows.slice(0, limit), next: rows.length > limit ? String(skip + limit) : null };
|
|
97
|
+
}
|
|
98
|
+
const SALES_STATUS = { A: "open", B: "in process", C: "completed" };
|
|
99
|
+
const DELIVERY_STATUS = {
|
|
100
|
+
A: "not delivered",
|
|
101
|
+
B: "partly delivered",
|
|
102
|
+
C: "fully delivered",
|
|
103
|
+
};
|
|
104
|
+
const PO_STATUS = {
|
|
105
|
+
"01": "draft",
|
|
106
|
+
"02": "active",
|
|
107
|
+
"03": "in approval",
|
|
108
|
+
"04": "partly approved",
|
|
109
|
+
"05": "approved",
|
|
110
|
+
"08": "rejected",
|
|
111
|
+
};
|
|
112
|
+
const INVOICE_STATUS = {
|
|
113
|
+
"5": "posted",
|
|
114
|
+
A: "parked",
|
|
115
|
+
B: "parked and completed",
|
|
116
|
+
D: "held",
|
|
117
|
+
E: "parked and held",
|
|
118
|
+
};
|
|
119
|
+
const coded = (map, v) => typeof v === "string" && v !== "" ? (map[v] ?? v) : null;
|
|
120
|
+
const SALES_HEADER = [
|
|
121
|
+
"SalesOrder",
|
|
122
|
+
"SalesOrderType",
|
|
123
|
+
"SalesOrganization",
|
|
124
|
+
"DistributionChannel",
|
|
125
|
+
"OrganizationDivision",
|
|
126
|
+
"SoldToParty",
|
|
127
|
+
"PurchaseOrderByCustomer",
|
|
128
|
+
"CreationDate",
|
|
129
|
+
"RequestedDeliveryDate",
|
|
130
|
+
"TotalNetAmount",
|
|
131
|
+
"TransactionCurrency",
|
|
132
|
+
"OverallSDProcessStatus",
|
|
133
|
+
"OverallTotalDeliveryStatus",
|
|
134
|
+
];
|
|
135
|
+
const salesOrder = (o) => ({
|
|
136
|
+
sales_order: o.SalesOrder,
|
|
137
|
+
type: o.SalesOrderType,
|
|
138
|
+
sales_organization: o.SalesOrganization,
|
|
139
|
+
distribution_channel: o.DistributionChannel,
|
|
140
|
+
division: o.OrganizationDivision,
|
|
141
|
+
sold_to: o.SoldToParty,
|
|
142
|
+
customer_reference: text(o.PurchaseOrderByCustomer),
|
|
143
|
+
created: day(o.CreationDate),
|
|
144
|
+
requested_delivery: day(o.RequestedDeliveryDate),
|
|
145
|
+
net_amount: num(o.TotalNetAmount),
|
|
146
|
+
currency: o.TransactionCurrency,
|
|
147
|
+
status: coded(SALES_STATUS, o.OverallSDProcessStatus),
|
|
148
|
+
delivery_status: coded(DELIVERY_STATUS, o.OverallTotalDeliveryStatus),
|
|
149
|
+
});
|
|
150
|
+
const PO_HEADER = [
|
|
151
|
+
"PurchaseOrder",
|
|
152
|
+
"PurchaseOrderType",
|
|
153
|
+
"CompanyCode",
|
|
154
|
+
"PurchasingOrganization",
|
|
155
|
+
"PurchasingGroup",
|
|
156
|
+
"Supplier",
|
|
157
|
+
"PurchaseOrderDate",
|
|
158
|
+
"DocumentCurrency",
|
|
159
|
+
"PurchasingProcessingStatus",
|
|
160
|
+
"PurchasingDocumentDeletionCode",
|
|
161
|
+
];
|
|
162
|
+
const purchaseOrder = (o) => ({
|
|
163
|
+
purchase_order: o.PurchaseOrder,
|
|
164
|
+
type: o.PurchaseOrderType,
|
|
165
|
+
company_code: o.CompanyCode,
|
|
166
|
+
purchasing_organization: o.PurchasingOrganization,
|
|
167
|
+
purchasing_group: o.PurchasingGroup,
|
|
168
|
+
supplier: o.Supplier,
|
|
169
|
+
date: day(o.PurchaseOrderDate),
|
|
170
|
+
currency: o.DocumentCurrency,
|
|
171
|
+
status: coded(PO_STATUS, o.PurchasingProcessingStatus),
|
|
172
|
+
deleted: Boolean(o.PurchasingDocumentDeletionCode),
|
|
173
|
+
});
|
|
174
|
+
const inPurchasingLimits = (ctx, o) => allowedValue(ctx, "company_codes", o.CompanyCode) &&
|
|
175
|
+
allowedValue(ctx, "purchasing_organizations", o.PurchasingOrganization);
|
|
176
|
+
// Plants of new order lines: with a plants limit each line names one of them.
|
|
177
|
+
function checkLinePlants(ctx, plants) {
|
|
178
|
+
const allowed = listLimit(ctx.limits, "plants");
|
|
179
|
+
if (!allowed)
|
|
180
|
+
return;
|
|
181
|
+
for (const p of plants) {
|
|
182
|
+
if (!p)
|
|
183
|
+
throw new Denied("Give a plant on every line: this agent is limited to some plants");
|
|
184
|
+
if (!allowed.includes(p))
|
|
185
|
+
throw new Denied(`This agent is not allowed to use plant ${p}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function checkPlants(ctx, items, field, what) {
|
|
189
|
+
if (!items.every((i) => !i[field] || allowedValue(ctx, "plants", i[field])))
|
|
190
|
+
throw new Denied(`${what} has lines at a plant this agent is not allowed to use`);
|
|
191
|
+
}
|
|
192
|
+
const quantity = z.number().positive().max(1e9);
|
|
193
|
+
export const sapTools = {
|
|
194
|
+
names: [
|
|
195
|
+
"sap_find_business_partners",
|
|
196
|
+
"sap_list_sales_orders",
|
|
197
|
+
"sap_get_sales_order",
|
|
198
|
+
"sap_create_sales_order",
|
|
199
|
+
"sap_list_purchase_orders",
|
|
200
|
+
"sap_get_purchase_order",
|
|
201
|
+
"sap_create_purchase_order",
|
|
202
|
+
"sap_find_products",
|
|
203
|
+
"sap_get_stock",
|
|
204
|
+
"sap_list_supplier_invoices",
|
|
205
|
+
"sap_list_account_items",
|
|
206
|
+
],
|
|
207
|
+
register(server, ctx, allowed) {
|
|
208
|
+
const readOnly = { readOnlyHint: true, openWorldHint: true };
|
|
209
|
+
const write = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
|
|
210
|
+
const org = (what) => z.string().regex(ORG).describe(what);
|
|
211
|
+
const doc = (what) => z.string().regex(DOC).describe(what);
|
|
212
|
+
const date = (what) => z.string().describe(`${what}, YYYY-MM-DD.`);
|
|
213
|
+
const target = (args) => JSON.stringify(args).slice(0, 300);
|
|
214
|
+
if (allowed.has("sap_find_business_partners"))
|
|
215
|
+
server.registerTool("sap_find_business_partners", {
|
|
216
|
+
description: "Find business partners (customers and suppliers) by name or number. The name match is a case-sensitive part of the full name.",
|
|
217
|
+
inputSchema: z.object({
|
|
218
|
+
name: z.string().min(2).max(80).optional(),
|
|
219
|
+
partner: doc("Business partner, customer or supplier number.").optional(),
|
|
220
|
+
role: z.enum(["any", "customer", "supplier"]).default("any"),
|
|
221
|
+
...paging,
|
|
222
|
+
}),
|
|
223
|
+
annotations: readOnly,
|
|
224
|
+
}, (args) => audited(ctx, "sap_find_business_partners", target(args), async (mark) => {
|
|
225
|
+
const filters = [
|
|
226
|
+
...(args.partner
|
|
227
|
+
? [
|
|
228
|
+
`(${["BusinessPartner", "Customer", "Supplier"].map((f) => `${f} eq ${lit(args.partner)}`).join(" or ")})`,
|
|
229
|
+
]
|
|
230
|
+
: []),
|
|
231
|
+
...(args.name ? [`substringof(${lit(args.name)},BusinessPartnerFullName)`] : []),
|
|
232
|
+
...(args.role === "customer" ? ["Customer ne ''"] : []),
|
|
233
|
+
...(args.role === "supplier" ? ["Supplier ne ''"] : []),
|
|
234
|
+
];
|
|
235
|
+
const { rows, next } = await page(ctx, mark, `${BP}/A_BusinessPartner`, {
|
|
236
|
+
select: [
|
|
237
|
+
"BusinessPartner",
|
|
238
|
+
"BusinessPartnerFullName",
|
|
239
|
+
"BusinessPartnerCategory",
|
|
240
|
+
"BusinessPartnerIsBlocked",
|
|
241
|
+
"Customer",
|
|
242
|
+
"Supplier",
|
|
243
|
+
"to_BusinessPartnerAddress/CityName",
|
|
244
|
+
"to_BusinessPartnerAddress/Country",
|
|
245
|
+
],
|
|
246
|
+
expand: "to_BusinessPartnerAddress",
|
|
247
|
+
filters,
|
|
248
|
+
orderby: "BusinessPartner",
|
|
249
|
+
}, args.cursor, args.limit);
|
|
250
|
+
return json({
|
|
251
|
+
partners: rows.map((p) => {
|
|
252
|
+
const address = results(p.to_BusinessPartnerAddress)[0];
|
|
253
|
+
return {
|
|
254
|
+
partner: p.BusinessPartner,
|
|
255
|
+
name: p.BusinessPartnerFullName,
|
|
256
|
+
kind: { "1": "person", "2": "organization", "3": "group" }[p.BusinessPartnerCategory] ?? null,
|
|
257
|
+
customer: text(p.Customer),
|
|
258
|
+
supplier: text(p.Supplier),
|
|
259
|
+
blocked: Boolean(p.BusinessPartnerIsBlocked),
|
|
260
|
+
city: text(address?.CityName),
|
|
261
|
+
country: text(address?.Country),
|
|
262
|
+
};
|
|
263
|
+
}),
|
|
264
|
+
next,
|
|
265
|
+
});
|
|
266
|
+
}));
|
|
267
|
+
if (allowed.has("sap_list_sales_orders"))
|
|
268
|
+
server.registerTool("sap_list_sales_orders", {
|
|
269
|
+
description: "List sales orders, newest first, with their status and delivery status. Filter by sales organization, customer (sold-to party), the customer's own reference or creation date.",
|
|
270
|
+
inputSchema: z.object({
|
|
271
|
+
sales_organization: org("Sales organization, e.g. 1010.").optional(),
|
|
272
|
+
sold_to: doc("Customer number of the sold-to party.").optional(),
|
|
273
|
+
customer_reference: z
|
|
274
|
+
.string()
|
|
275
|
+
.min(1)
|
|
276
|
+
.max(35)
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("The customer's purchase order number."),
|
|
279
|
+
created_from: date("Created on or after").optional(),
|
|
280
|
+
created_to: date("Created on or before").optional(),
|
|
281
|
+
...paging,
|
|
282
|
+
}),
|
|
283
|
+
annotations: readOnly,
|
|
284
|
+
}, (args) => audited(ctx, "sap_list_sales_orders", target(args), async (mark) => {
|
|
285
|
+
checkDay(args.created_from);
|
|
286
|
+
checkDay(args.created_to);
|
|
287
|
+
const orgs = scope(ctx, "sales_organizations", args.sales_organization, "sales organization");
|
|
288
|
+
const filters = [
|
|
289
|
+
...(orgs ? [anyOf("SalesOrganization", orgs)] : []),
|
|
290
|
+
...(args.sold_to ? [`SoldToParty eq ${lit(args.sold_to)}`] : []),
|
|
291
|
+
...(args.customer_reference
|
|
292
|
+
? [`PurchaseOrderByCustomer eq ${lit(args.customer_reference)}`]
|
|
293
|
+
: []),
|
|
294
|
+
...(args.created_from ? [since("CreationDate", args.created_from)] : []),
|
|
295
|
+
...(args.created_to ? [until("CreationDate", args.created_to)] : []),
|
|
296
|
+
];
|
|
297
|
+
const { rows, next } = await page(ctx, mark, `${SALES}/A_SalesOrder`, { select: SALES_HEADER, filters, orderby: "SalesOrder desc" }, args.cursor, args.limit);
|
|
298
|
+
return json({
|
|
299
|
+
sales_orders: rows
|
|
300
|
+
.filter((o) => allowedValue(ctx, "sales_organizations", o.SalesOrganization))
|
|
301
|
+
.map(salesOrder),
|
|
302
|
+
next,
|
|
303
|
+
});
|
|
304
|
+
}));
|
|
305
|
+
if (allowed.has("sap_get_sales_order"))
|
|
306
|
+
server.registerTool("sap_get_sales_order", {
|
|
307
|
+
description: "Read one sales order with its lines, status and delivery status.",
|
|
308
|
+
inputSchema: z.object({ sales_order: doc("Sales order number.") }),
|
|
309
|
+
annotations: readOnly,
|
|
310
|
+
}, ({ sales_order }) => audited(ctx, "sap_get_sales_order", sales_order, async (mark) => {
|
|
311
|
+
const o = (await ok(ctx, mark, {
|
|
312
|
+
method: "GET",
|
|
313
|
+
path: `${SALES}/A_SalesOrder(${encodeURIComponent(lit(sales_order))})?$expand=to_Item`,
|
|
314
|
+
})).d;
|
|
315
|
+
if (!allowedValue(ctx, "sales_organizations", o?.SalesOrganization))
|
|
316
|
+
throw new Denied(`Sales order ${sales_order} is in a sales organization this agent is not allowed to use`);
|
|
317
|
+
const items = results(o.to_Item);
|
|
318
|
+
checkPlants(ctx, items, "ProductionPlant", `Sales order ${sales_order}`);
|
|
319
|
+
return json({
|
|
320
|
+
...salesOrder(o),
|
|
321
|
+
lines: items.map((i) => ({
|
|
322
|
+
line: i.SalesOrderItem,
|
|
323
|
+
material: i.Material,
|
|
324
|
+
description: text(i.SalesOrderItemText),
|
|
325
|
+
quantity: num(i.RequestedQuantity),
|
|
326
|
+
unit: i.RequestedQuantityUnit,
|
|
327
|
+
net_amount: num(i.NetAmount),
|
|
328
|
+
plant: text(i.ProductionPlant),
|
|
329
|
+
status: coded(SALES_STATUS, i.SDProcessStatus),
|
|
330
|
+
delivery_status: coded(DELIVERY_STATUS, i.DeliveryStatus),
|
|
331
|
+
})),
|
|
332
|
+
});
|
|
333
|
+
}));
|
|
334
|
+
if (allowed.has("sap_create_sales_order"))
|
|
335
|
+
server.registerTool("sap_create_sales_order", {
|
|
336
|
+
description: "Create a sales order for a customer from structured lines (material and quantity). SAP fills prices, partners and schedule from the customer and material master data.",
|
|
337
|
+
inputSchema: z.object({
|
|
338
|
+
sales_organization: org("Sales organization."),
|
|
339
|
+
distribution_channel: z
|
|
340
|
+
.string()
|
|
341
|
+
.regex(/^[A-Za-z0-9]{2}$/)
|
|
342
|
+
.describe("Distribution channel, e.g. 10."),
|
|
343
|
+
division: z
|
|
344
|
+
.string()
|
|
345
|
+
.regex(/^[A-Za-z0-9]{2}$/)
|
|
346
|
+
.describe("Division, e.g. 00."),
|
|
347
|
+
sold_to: doc("Customer number of the sold-to party."),
|
|
348
|
+
order_type: z
|
|
349
|
+
.string()
|
|
350
|
+
.regex(/^[A-Za-z0-9]{1,4}$/)
|
|
351
|
+
.default("OR")
|
|
352
|
+
.describe("Sales order type; OR is a standard order."),
|
|
353
|
+
customer_reference: z
|
|
354
|
+
.string()
|
|
355
|
+
.min(1)
|
|
356
|
+
.max(35)
|
|
357
|
+
.optional()
|
|
358
|
+
.describe("The customer's purchase order number."),
|
|
359
|
+
requested_delivery_date: date("Requested delivery date").optional(),
|
|
360
|
+
lines: z
|
|
361
|
+
.array(z.object({
|
|
362
|
+
material: z.string().regex(MATERIAL),
|
|
363
|
+
quantity,
|
|
364
|
+
unit: z
|
|
365
|
+
.string()
|
|
366
|
+
.regex(/^[A-Za-z0-9]{1,3}$/)
|
|
367
|
+
.optional()
|
|
368
|
+
.describe("Sales unit; the material's own if omitted."),
|
|
369
|
+
plant: org("Delivering plant.").optional(),
|
|
370
|
+
}))
|
|
371
|
+
.min(1)
|
|
372
|
+
.max(50),
|
|
373
|
+
}),
|
|
374
|
+
annotations: write,
|
|
375
|
+
}, (args) => audited(ctx, "sap_create_sales_order", `${args.sales_organization}/${args.sold_to}`, async (mark, retarget) => {
|
|
376
|
+
checkDay(args.requested_delivery_date);
|
|
377
|
+
scope(ctx, "sales_organizations", args.sales_organization, "sales organization");
|
|
378
|
+
checkLinePlants(ctx, args.lines.map((l) => l.plant));
|
|
379
|
+
const created = (await ok(ctx, mark, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
path: `${SALES}/A_SalesOrder`,
|
|
382
|
+
body: {
|
|
383
|
+
SalesOrderType: args.order_type,
|
|
384
|
+
SalesOrganization: args.sales_organization,
|
|
385
|
+
DistributionChannel: args.distribution_channel,
|
|
386
|
+
OrganizationDivision: args.division,
|
|
387
|
+
SoldToParty: args.sold_to,
|
|
388
|
+
...(args.customer_reference
|
|
389
|
+
? { PurchaseOrderByCustomer: args.customer_reference }
|
|
390
|
+
: {}),
|
|
391
|
+
...(args.requested_delivery_date
|
|
392
|
+
? { RequestedDeliveryDate: sapDate(args.requested_delivery_date) }
|
|
393
|
+
: {}),
|
|
394
|
+
to_Item: args.lines.map((l, i) => ({
|
|
395
|
+
SalesOrderItem: String((i + 1) * 10),
|
|
396
|
+
Material: l.material,
|
|
397
|
+
RequestedQuantity: String(l.quantity),
|
|
398
|
+
...(l.unit ? { RequestedQuantityUnit: l.unit } : {}),
|
|
399
|
+
...(l.plant ? { ProductionPlant: l.plant } : {}),
|
|
400
|
+
})),
|
|
401
|
+
},
|
|
402
|
+
})).d;
|
|
403
|
+
retarget(`${args.sales_organization}/${args.sold_to} sales order ${created?.SalesOrder}`);
|
|
404
|
+
return json({
|
|
405
|
+
sales_order: created?.SalesOrder ?? null,
|
|
406
|
+
net_amount: num(created?.TotalNetAmount),
|
|
407
|
+
currency: created?.TransactionCurrency ?? null,
|
|
408
|
+
status: coded(SALES_STATUS, created?.OverallSDProcessStatus),
|
|
409
|
+
});
|
|
410
|
+
}));
|
|
411
|
+
if (allowed.has("sap_list_purchase_orders"))
|
|
412
|
+
server.registerTool("sap_list_purchase_orders", {
|
|
413
|
+
description: "List purchase orders, newest first, with their approval status. Filter by company code, purchasing organization, supplier or order date.",
|
|
414
|
+
inputSchema: z.object({
|
|
415
|
+
company_code: org("Company code, e.g. 1010.").optional(),
|
|
416
|
+
purchasing_organization: org("Purchasing organization.").optional(),
|
|
417
|
+
supplier: doc("Supplier number.").optional(),
|
|
418
|
+
ordered_from: date("Ordered on or after").optional(),
|
|
419
|
+
ordered_to: date("Ordered on or before").optional(),
|
|
420
|
+
...paging,
|
|
421
|
+
}),
|
|
422
|
+
annotations: readOnly,
|
|
423
|
+
}, (args) => audited(ctx, "sap_list_purchase_orders", target(args), async (mark) => {
|
|
424
|
+
checkDay(args.ordered_from);
|
|
425
|
+
checkDay(args.ordered_to);
|
|
426
|
+
const codes = scope(ctx, "company_codes", args.company_code, "company code");
|
|
427
|
+
const porgs = scope(ctx, "purchasing_organizations", args.purchasing_organization, "purchasing organization");
|
|
428
|
+
const filters = [
|
|
429
|
+
...(codes ? [anyOf("CompanyCode", codes)] : []),
|
|
430
|
+
...(porgs ? [anyOf("PurchasingOrganization", porgs)] : []),
|
|
431
|
+
...(args.supplier ? [`Supplier eq ${lit(args.supplier)}`] : []),
|
|
432
|
+
...(args.ordered_from ? [since("PurchaseOrderDate", args.ordered_from)] : []),
|
|
433
|
+
...(args.ordered_to ? [until("PurchaseOrderDate", args.ordered_to)] : []),
|
|
434
|
+
];
|
|
435
|
+
const { rows, next } = await page(ctx, mark, `${PURCHASING}/A_PurchaseOrder`, { select: PO_HEADER, filters, orderby: "PurchaseOrder desc" }, args.cursor, args.limit);
|
|
436
|
+
return json({
|
|
437
|
+
purchase_orders: rows.filter((o) => inPurchasingLimits(ctx, o)).map(purchaseOrder),
|
|
438
|
+
next,
|
|
439
|
+
});
|
|
440
|
+
}));
|
|
441
|
+
if (allowed.has("sap_get_purchase_order"))
|
|
442
|
+
server.registerTool("sap_get_purchase_order", {
|
|
443
|
+
description: "Read one purchase order with its lines: price, quantity, plant, and whether each line is delivered and invoiced.",
|
|
444
|
+
inputSchema: z.object({ purchase_order: doc("Purchase order number.") }),
|
|
445
|
+
annotations: readOnly,
|
|
446
|
+
}, ({ purchase_order }) => audited(ctx, "sap_get_purchase_order", purchase_order, async (mark) => {
|
|
447
|
+
const o = (await ok(ctx, mark, {
|
|
448
|
+
method: "GET",
|
|
449
|
+
path: `${PURCHASING}/A_PurchaseOrder(${encodeURIComponent(lit(purchase_order))})?$expand=to_PurchaseOrderItem`,
|
|
450
|
+
})).d;
|
|
451
|
+
if (!o || !inPurchasingLimits(ctx, o))
|
|
452
|
+
throw new Denied(`Purchase order ${purchase_order} is in a company code or purchasing organization this agent is not allowed to use`);
|
|
453
|
+
const items = results(o.to_PurchaseOrderItem);
|
|
454
|
+
checkPlants(ctx, items, "Plant", `Purchase order ${purchase_order}`);
|
|
455
|
+
return json({
|
|
456
|
+
...purchaseOrder(o),
|
|
457
|
+
lines: items.map((i) => ({
|
|
458
|
+
line: i.PurchaseOrderItem,
|
|
459
|
+
material: text(i.Material),
|
|
460
|
+
description: text(i.PurchaseOrderItemText),
|
|
461
|
+
plant: text(i.Plant),
|
|
462
|
+
quantity: num(i.OrderQuantity),
|
|
463
|
+
unit: i.PurchaseOrderQuantityUnit,
|
|
464
|
+
net_price: num(i.NetPriceAmount),
|
|
465
|
+
price_per: num(i.NetPriceQuantity),
|
|
466
|
+
delivered: Boolean(i.IsCompletelyDelivered),
|
|
467
|
+
invoiced: Boolean(i.IsFinallyInvoiced),
|
|
468
|
+
deleted: Boolean(i.PurchasingDocumentDeletionCode),
|
|
469
|
+
})),
|
|
470
|
+
});
|
|
471
|
+
}));
|
|
472
|
+
if (allowed.has("sap_create_purchase_order"))
|
|
473
|
+
server.registerTool("sap_create_purchase_order", {
|
|
474
|
+
description: "Create a purchase order to a supplier. Each line names a material, or a description and material group for an item without a material. It goes through your purchase order approval like any other; this tool cannot approve it.",
|
|
475
|
+
inputSchema: z.object({
|
|
476
|
+
company_code: org("Company code."),
|
|
477
|
+
purchasing_organization: org("Purchasing organization."),
|
|
478
|
+
purchasing_group: z
|
|
479
|
+
.string()
|
|
480
|
+
.regex(/^[A-Za-z0-9]{1,3}$/)
|
|
481
|
+
.describe("Purchasing group."),
|
|
482
|
+
supplier: doc("Supplier number."),
|
|
483
|
+
order_type: z
|
|
484
|
+
.string()
|
|
485
|
+
.regex(/^[A-Za-z0-9]{1,4}$/)
|
|
486
|
+
.default("NB")
|
|
487
|
+
.describe("Purchase order type; NB is a standard order."),
|
|
488
|
+
currency: z
|
|
489
|
+
.string()
|
|
490
|
+
.regex(/^[A-Z]{3}$/)
|
|
491
|
+
.optional()
|
|
492
|
+
.describe("Order currency; the supplier's if omitted."),
|
|
493
|
+
lines: z
|
|
494
|
+
.array(z.object({
|
|
495
|
+
material: z.string().regex(MATERIAL).optional(),
|
|
496
|
+
description: z.string().min(1).max(40).optional(),
|
|
497
|
+
material_group: z
|
|
498
|
+
.string()
|
|
499
|
+
.regex(/^[A-Za-z0-9]{1,9}$/)
|
|
500
|
+
.optional(),
|
|
501
|
+
plant: org("Receiving plant."),
|
|
502
|
+
quantity,
|
|
503
|
+
unit: z
|
|
504
|
+
.string()
|
|
505
|
+
.regex(/^[A-Za-z0-9]{1,3}$/)
|
|
506
|
+
.optional()
|
|
507
|
+
.describe("Order unit; the material's own if omitted."),
|
|
508
|
+
net_price: z
|
|
509
|
+
.number()
|
|
510
|
+
.nonnegative()
|
|
511
|
+
.max(1e12)
|
|
512
|
+
.optional()
|
|
513
|
+
.describe("Price per unit; from the info record if omitted."),
|
|
514
|
+
}))
|
|
515
|
+
.min(1)
|
|
516
|
+
.max(50),
|
|
517
|
+
}),
|
|
518
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
519
|
+
}, (args) => audited(ctx, "sap_create_purchase_order", `${args.company_code}/${args.supplier}`, async (mark, retarget) => {
|
|
520
|
+
scope(ctx, "company_codes", args.company_code, "company code");
|
|
521
|
+
scope(ctx, "purchasing_organizations", args.purchasing_organization, "purchasing organization");
|
|
522
|
+
checkLinePlants(ctx, args.lines.map((l) => l.plant));
|
|
523
|
+
if (args.lines.some((l) => !l.material && !(l.description && l.material_group)))
|
|
524
|
+
throw new Denied("Give each line a material, or a description and a material group");
|
|
525
|
+
const created = (await ok(ctx, mark, {
|
|
526
|
+
method: "POST",
|
|
527
|
+
path: `${PURCHASING}/A_PurchaseOrder`,
|
|
528
|
+
body: {
|
|
529
|
+
PurchaseOrderType: args.order_type,
|
|
530
|
+
CompanyCode: args.company_code,
|
|
531
|
+
PurchasingOrganization: args.purchasing_organization,
|
|
532
|
+
PurchasingGroup: args.purchasing_group,
|
|
533
|
+
Supplier: args.supplier,
|
|
534
|
+
...(args.currency ? { DocumentCurrency: args.currency } : {}),
|
|
535
|
+
to_PurchaseOrderItem: args.lines.map((l, i) => ({
|
|
536
|
+
PurchaseOrderItem: String((i + 1) * 10),
|
|
537
|
+
Plant: l.plant,
|
|
538
|
+
OrderQuantity: String(l.quantity),
|
|
539
|
+
...(l.material ? { Material: l.material } : {}),
|
|
540
|
+
...(l.description ? { PurchaseOrderItemText: l.description } : {}),
|
|
541
|
+
...(l.material_group ? { MaterialGroup: l.material_group } : {}),
|
|
542
|
+
...(l.unit ? { PurchaseOrderQuantityUnit: l.unit } : {}),
|
|
543
|
+
...(l.net_price !== undefined ? { NetPriceAmount: String(l.net_price) } : {}),
|
|
544
|
+
})),
|
|
545
|
+
},
|
|
546
|
+
})).d;
|
|
547
|
+
retarget(`${args.company_code}/${args.supplier} purchase order ${created?.PurchaseOrder}`);
|
|
548
|
+
return json({
|
|
549
|
+
purchase_order: created?.PurchaseOrder ?? null,
|
|
550
|
+
status: coded(PO_STATUS, created?.PurchasingProcessingStatus),
|
|
551
|
+
});
|
|
552
|
+
}));
|
|
553
|
+
if (allowed.has("sap_find_products"))
|
|
554
|
+
server.registerTool("sap_find_products", {
|
|
555
|
+
description: "Find products (materials) by number or by a case-sensitive part of their English description.",
|
|
556
|
+
inputSchema: z.object({
|
|
557
|
+
product: z.string().regex(MATERIAL).optional().describe("Product number."),
|
|
558
|
+
description: z.string().min(2).max(40).optional(),
|
|
559
|
+
...paging,
|
|
560
|
+
}),
|
|
561
|
+
annotations: readOnly,
|
|
562
|
+
}, (args) => audited(ctx, "sap_find_products", target(args), async (mark) => {
|
|
563
|
+
const filters = [
|
|
564
|
+
"Language eq 'EN'",
|
|
565
|
+
...(args.product ? [`Product eq ${lit(args.product)}`] : []),
|
|
566
|
+
...(args.description
|
|
567
|
+
? [`substringof(${lit(args.description)},ProductDescription)`]
|
|
568
|
+
: []),
|
|
569
|
+
];
|
|
570
|
+
const { rows, next } = await page(ctx, mark, `${PRODUCT}/A_ProductDescription`, { select: ["Product", "ProductDescription"], filters, orderby: "Product" }, args.cursor, args.limit);
|
|
571
|
+
return json({
|
|
572
|
+
products: rows.map((p) => ({
|
|
573
|
+
product: p.Product,
|
|
574
|
+
description: p.ProductDescription,
|
|
575
|
+
})),
|
|
576
|
+
next,
|
|
577
|
+
});
|
|
578
|
+
}));
|
|
579
|
+
if (allowed.has("sap_get_stock"))
|
|
580
|
+
server.registerTool("sap_get_stock", {
|
|
581
|
+
description: "Stock of one material by plant, storage location and batch, with the unrestricted (available) quantity per plant.",
|
|
582
|
+
inputSchema: z.object({
|
|
583
|
+
material: z.string().regex(MATERIAL).describe("Material (product) number."),
|
|
584
|
+
plant: org("Plant; every plant this agent may use if omitted.").optional(),
|
|
585
|
+
}),
|
|
586
|
+
annotations: readOnly,
|
|
587
|
+
}, (args) => audited(ctx, "sap_get_stock", target(args), async (mark) => {
|
|
588
|
+
const plants = scope(ctx, "plants", args.plant, "plant");
|
|
589
|
+
const { rows, next } = await page(ctx, mark, `${STOCK}/A_MatlStkInAcctMod`, {
|
|
590
|
+
select: [
|
|
591
|
+
"Material",
|
|
592
|
+
"Plant",
|
|
593
|
+
"StorageLocation",
|
|
594
|
+
"Batch",
|
|
595
|
+
"InventoryStockType",
|
|
596
|
+
"InventorySpecialStockType",
|
|
597
|
+
"MatlWrhsStkQtyInMatlBaseUnit",
|
|
598
|
+
"MaterialBaseUnit",
|
|
599
|
+
],
|
|
600
|
+
filters: [
|
|
601
|
+
`Material eq ${lit(args.material)}`,
|
|
602
|
+
...(plants ? [anyOf("Plant", plants)] : []),
|
|
603
|
+
],
|
|
604
|
+
orderby: "Plant",
|
|
605
|
+
}, undefined, 200);
|
|
606
|
+
const kept = rows.filter((r) => allowedValue(ctx, "plants", r.Plant));
|
|
607
|
+
const unrestricted = {};
|
|
608
|
+
for (const r of kept)
|
|
609
|
+
if (r.InventoryStockType === "01" && !r.InventorySpecialStockType)
|
|
610
|
+
unrestricted[r.Plant] =
|
|
611
|
+
(unrestricted[r.Plant] ?? 0) + Number(r.MatlWrhsStkQtyInMatlBaseUnit);
|
|
612
|
+
return json({
|
|
613
|
+
material: args.material,
|
|
614
|
+
unit: kept[0]?.MaterialBaseUnit ?? null,
|
|
615
|
+
unrestricted_by_plant: unrestricted,
|
|
616
|
+
stock: kept.map((r) => ({
|
|
617
|
+
plant: r.Plant,
|
|
618
|
+
storage_location: text(r.StorageLocation),
|
|
619
|
+
batch: text(r.Batch),
|
|
620
|
+
stock_type: coded({ "01": "unrestricted", "02": "quality inspection" }, r.InventoryStockType),
|
|
621
|
+
special_stock: text(r.InventorySpecialStockType),
|
|
622
|
+
quantity: num(r.MatlWrhsStkQtyInMatlBaseUnit),
|
|
623
|
+
})),
|
|
624
|
+
more: next !== null,
|
|
625
|
+
});
|
|
626
|
+
}));
|
|
627
|
+
if (allowed.has("sap_list_supplier_invoices"))
|
|
628
|
+
server.registerTool("sap_list_supplier_invoices", {
|
|
629
|
+
description: "List supplier invoices, newest posting first, with amount, status and any payment block. Filter by company code, supplier, the supplier's invoice number or posting date. Use sap_list_account_items to see whether they are paid.",
|
|
630
|
+
inputSchema: z.object({
|
|
631
|
+
company_code: org("Company code.").optional(),
|
|
632
|
+
supplier: doc("Supplier (invoicing party) number.").optional(),
|
|
633
|
+
supplier_reference: z
|
|
634
|
+
.string()
|
|
635
|
+
.min(1)
|
|
636
|
+
.max(16)
|
|
637
|
+
.optional()
|
|
638
|
+
.describe("The supplier's own invoice number."),
|
|
639
|
+
posted_from: date("Posted on or after").optional(),
|
|
640
|
+
posted_to: date("Posted on or before").optional(),
|
|
641
|
+
...paging,
|
|
642
|
+
}),
|
|
643
|
+
annotations: readOnly,
|
|
644
|
+
}, (args) => audited(ctx, "sap_list_supplier_invoices", target(args), async (mark) => {
|
|
645
|
+
checkDay(args.posted_from);
|
|
646
|
+
checkDay(args.posted_to);
|
|
647
|
+
const codes = scope(ctx, "company_codes", args.company_code, "company code");
|
|
648
|
+
const filters = [
|
|
649
|
+
...(codes ? [anyOf("CompanyCode", codes)] : []),
|
|
650
|
+
...(args.supplier ? [`InvoicingParty eq ${lit(args.supplier)}`] : []),
|
|
651
|
+
...(args.supplier_reference
|
|
652
|
+
? [`SupplierInvoiceIDByInvcgParty eq ${lit(args.supplier_reference)}`]
|
|
653
|
+
: []),
|
|
654
|
+
...(args.posted_from ? [since("PostingDate", args.posted_from)] : []),
|
|
655
|
+
...(args.posted_to ? [until("PostingDate", args.posted_to)] : []),
|
|
656
|
+
];
|
|
657
|
+
const { rows, next } = await page(ctx, mark, `${INVOICES}/A_SupplierInvoice`, {
|
|
658
|
+
select: [
|
|
659
|
+
"SupplierInvoice",
|
|
660
|
+
"FiscalYear",
|
|
661
|
+
"CompanyCode",
|
|
662
|
+
"InvoicingParty",
|
|
663
|
+
"SupplierInvoiceIDByInvcgParty",
|
|
664
|
+
"DocumentDate",
|
|
665
|
+
"PostingDate",
|
|
666
|
+
"InvoiceGrossAmount",
|
|
667
|
+
"DocumentCurrency",
|
|
668
|
+
"SupplierInvoiceStatus",
|
|
669
|
+
"PaymentBlockingReason",
|
|
670
|
+
],
|
|
671
|
+
filters,
|
|
672
|
+
orderby: "PostingDate desc",
|
|
673
|
+
}, args.cursor, args.limit);
|
|
674
|
+
return json({
|
|
675
|
+
invoices: rows
|
|
676
|
+
.filter((r) => allowedValue(ctx, "company_codes", r.CompanyCode))
|
|
677
|
+
.map((r) => ({
|
|
678
|
+
invoice: r.SupplierInvoice,
|
|
679
|
+
fiscal_year: r.FiscalYear,
|
|
680
|
+
company_code: r.CompanyCode,
|
|
681
|
+
supplier: r.InvoicingParty,
|
|
682
|
+
supplier_reference: text(r.SupplierInvoiceIDByInvcgParty),
|
|
683
|
+
document_date: day(r.DocumentDate),
|
|
684
|
+
posted: day(r.PostingDate),
|
|
685
|
+
gross_amount: num(r.InvoiceGrossAmount),
|
|
686
|
+
currency: r.DocumentCurrency,
|
|
687
|
+
status: coded(INVOICE_STATUS, r.SupplierInvoiceStatus),
|
|
688
|
+
payment_block: text(r.PaymentBlockingReason),
|
|
689
|
+
})),
|
|
690
|
+
next,
|
|
691
|
+
});
|
|
692
|
+
}));
|
|
693
|
+
if (allowed.has("sap_list_account_items"))
|
|
694
|
+
server.registerTool("sap_list_account_items", {
|
|
695
|
+
description: "List open or paid (cleared) customer or supplier items in accounting, such as invoices and payments, with due date and the clearing document that paid them.",
|
|
696
|
+
inputSchema: z.object({
|
|
697
|
+
side: z.enum(["customer", "supplier"]),
|
|
698
|
+
account: doc("Customer or supplier number.").optional(),
|
|
699
|
+
company_code: org("Company code.").optional(),
|
|
700
|
+
status: z.enum(["open", "cleared", "all"]).default("open"),
|
|
701
|
+
due_to: date("Due on or before").optional(),
|
|
702
|
+
...paging,
|
|
703
|
+
}),
|
|
704
|
+
annotations: readOnly,
|
|
705
|
+
}, (args) => audited(ctx, "sap_list_account_items", target(args), async (mark) => {
|
|
706
|
+
checkDay(args.due_to);
|
|
707
|
+
const codes = scope(ctx, "company_codes", args.company_code, "company code");
|
|
708
|
+
const party = args.side === "customer" ? "Customer" : "Supplier";
|
|
709
|
+
const filters = [
|
|
710
|
+
`FinancialAccountType eq '${args.side === "customer" ? "D" : "K"}'`,
|
|
711
|
+
...(codes ? [anyOf("CompanyCode", codes)] : []),
|
|
712
|
+
...(args.account ? [`${party} eq ${lit(args.account)}`] : []),
|
|
713
|
+
...(args.status === "open" ? ["IsCleared eq false"] : []),
|
|
714
|
+
...(args.status === "cleared" ? ["IsCleared eq true"] : []),
|
|
715
|
+
...(args.due_to ? [until("NetDueDate", args.due_to)] : []),
|
|
716
|
+
];
|
|
717
|
+
const { rows, next } = await page(ctx, mark, `${ITEMS}/A_OperationalAcctgDocItemCube`, {
|
|
718
|
+
select: [
|
|
719
|
+
"CompanyCode",
|
|
720
|
+
"FiscalYear",
|
|
721
|
+
"AccountingDocument",
|
|
722
|
+
"AccountingDocumentItem",
|
|
723
|
+
"AccountingDocumentType",
|
|
724
|
+
party,
|
|
725
|
+
"DocumentReferenceID",
|
|
726
|
+
"PostingDate",
|
|
727
|
+
"NetDueDate",
|
|
728
|
+
"AmountInTransactionCurrency",
|
|
729
|
+
"TransactionCurrency",
|
|
730
|
+
"IsCleared",
|
|
731
|
+
"ClearingAccountingDocument",
|
|
732
|
+
"ClearingDate",
|
|
733
|
+
],
|
|
734
|
+
filters,
|
|
735
|
+
orderby: "NetDueDate",
|
|
736
|
+
}, args.cursor, args.limit);
|
|
737
|
+
return json({
|
|
738
|
+
items: rows
|
|
739
|
+
.filter((r) => allowedValue(ctx, "company_codes", r.CompanyCode))
|
|
740
|
+
.map((r) => ({
|
|
741
|
+
company_code: r.CompanyCode,
|
|
742
|
+
fiscal_year: r.FiscalYear,
|
|
743
|
+
document: r.AccountingDocument,
|
|
744
|
+
line: r.AccountingDocumentItem,
|
|
745
|
+
document_type: r.AccountingDocumentType,
|
|
746
|
+
account: r[party],
|
|
747
|
+
reference: text(r.DocumentReferenceID),
|
|
748
|
+
posted: day(r.PostingDate),
|
|
749
|
+
due: day(r.NetDueDate),
|
|
750
|
+
amount: num(r.AmountInTransactionCurrency),
|
|
751
|
+
currency: r.TransactionCurrency,
|
|
752
|
+
cleared: Boolean(r.IsCleared),
|
|
753
|
+
clearing_document: text(r.ClearingAccountingDocument),
|
|
754
|
+
cleared_on: day(r.ClearingDate),
|
|
755
|
+
})),
|
|
756
|
+
next,
|
|
757
|
+
});
|
|
758
|
+
}));
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
//# sourceMappingURL=tools.js.map
|