pleaxy-mcp 0.1.1
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/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/api.js +75 -0
- package/dist/auth/credentials.js +52 -0
- package/dist/auth/oauth.js +164 -0
- package/dist/auth/session.js +41 -0
- package/dist/cli.js +76 -0
- package/dist/ids.js +15 -0
- package/dist/index.js +14 -0
- package/dist/purchase-order-shape.js +43 -0
- package/dist/purchase-request-shape.js +39 -0
- package/dist/questionnaire-shape.js +36 -0
- package/dist/receipt-shape.js +34 -0
- package/dist/rejection.js +37 -0
- package/dist/server.js +479 -0
- package/package.json +48 -0
- package/server.json +40 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const lineItemShape = {
|
|
3
|
+
scopeType: z.enum(["sku", "service", "account", "custom"]),
|
|
4
|
+
scopeId: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("For scopeType 'sku' on a PO to a supplier with a published Pleaxy catalog, this must be a CatalogItem id from list_supplier_catalog - the server rejects anything else. For other scope types it is free text."),
|
|
8
|
+
name: z.string(),
|
|
9
|
+
ruleType: z.enum(["quantity", "amount"]),
|
|
10
|
+
unit: z.string(),
|
|
11
|
+
unitPrice: z.number().min(0),
|
|
12
|
+
qty: z.number().min(0),
|
|
13
|
+
nteAmount: z.number().min(0),
|
|
14
|
+
currency: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Omit it: every line is in the region currency. Another currency is refused - Pleaxy doesn't convert currencies."),
|
|
18
|
+
paymentTerms: z
|
|
19
|
+
.string()
|
|
20
|
+
.max(40)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('e.g. "Net 30". Omit it unless the user gave terms: the line then takes the default payment terms of the contract it falls under (contractId, or the contract its catalog price came from), else the catalog item\'s, else "Net 30".'),
|
|
23
|
+
validFrom: z.string(),
|
|
24
|
+
validUntil: z.string(),
|
|
25
|
+
customerLineNo: z.string().max(40).optional().describe("Optional: the buyer's own number for this line (e.g. from their ERP), up to 40 letters, numbers, spaces, or . _ - /."),
|
|
26
|
+
};
|
|
27
|
+
export const createPurchaseOrderShape = {
|
|
28
|
+
supplier: z.string().describe("The supplier/counterparty you're purchasing from, e.g. \"aws\""),
|
|
29
|
+
supplierRegionId: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("regionId of the onboarded Pleaxy supplier this PO is issued to. The buyer must hold an approved SupplierConnection to this supplier; the server rejects a region it isn't connected to."),
|
|
33
|
+
provider: z.enum(["aws", "gcp", "azure", "oci"]).optional(),
|
|
34
|
+
billTo: z.string().optional(),
|
|
35
|
+
costCenter: z.string().optional(),
|
|
36
|
+
budgetOwner: z.string().optional(),
|
|
37
|
+
notes: z.string().optional(),
|
|
38
|
+
contractId: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("The buyer's own active contract with this supplier the order falls under (its id, e.g. CT-1A2B3C4D). Invoices against the order are checked against it, and lines that omit paymentTerms take its default payment terms."),
|
|
42
|
+
lineItems: z.array(z.object(lineItemShape)).min(1),
|
|
43
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const purchaseRequestLineShape = {
|
|
3
|
+
name: z.string().min(1).max(200).describe("What is needed, e.g. 27-inch monitor."),
|
|
4
|
+
qty: z.number().positive().describe("How many."),
|
|
5
|
+
unit: z.string().max(20).optional().describe("e.g. each, hour, month."),
|
|
6
|
+
unitPrice: z.number().min(0).describe("Roughly what one costs, in the request's currency. Ask the user; never guess a price."),
|
|
7
|
+
};
|
|
8
|
+
export const purchaseRequestSupplierShape = {
|
|
9
|
+
name: z.string().min(1).max(200).describe("The supplier's name as the user knows it."),
|
|
10
|
+
supplierConnectionId: z.string().max(64).optional().describe("Only if the supplier is on the workspace's supplier list and you know its connection id."),
|
|
11
|
+
supplierRegionId: z.string().max(64).optional(),
|
|
12
|
+
};
|
|
13
|
+
export const createPurchaseRequestShape = {
|
|
14
|
+
title: z.string().min(1).max(200).describe("What is needed, in a few words."),
|
|
15
|
+
description: z.string().max(4000).optional().describe("Why it is needed: the business reason, in the user's words. Required to submit."),
|
|
16
|
+
costCenter: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(200)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("A cost center code or name on the workspace's list (stored as its code). Required to submit when the workspace keeps a list."),
|
|
21
|
+
glCode: z.string().max(40).optional().describe("The GL account, only if the user gave one; otherwise the cost center's is used."),
|
|
22
|
+
supplier: z.object(purchaseRequestSupplierShape).optional().describe("A supplier the user suggests, only if they named one."),
|
|
23
|
+
lineItems: z.array(z.object(purchaseRequestLineShape)).min(1).max(50),
|
|
24
|
+
currency: z.string().length(3).optional().describe("ISO 4217; omit for the workspace's currency."),
|
|
25
|
+
neededBy: z
|
|
26
|
+
.string()
|
|
27
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("When it's needed, YYYY-MM-DD."),
|
|
30
|
+
notes: z.string().max(2000).optional().describe("Anything procurement should know."),
|
|
31
|
+
submit: z.boolean().optional().describe("true sends it for approval at once; otherwise it's saved as a draft the user can submit."),
|
|
32
|
+
};
|
|
33
|
+
export const convertPurchaseRequestShape = {
|
|
34
|
+
supplier: z.string().max(200).optional().describe("The supplier to order from. Required when the request names none."),
|
|
35
|
+
supplierRegionId: z.string().max(64).optional(),
|
|
36
|
+
supplierConnectionId: z.string().max(64).optional(),
|
|
37
|
+
draft: z.boolean().optional().describe("true creates a draft purchase order to review in Pleaxy; otherwise it's created and sent for approval (or issued, within what was approved)."),
|
|
38
|
+
notes: z.string().max(2000).optional(),
|
|
39
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const MAX_OPTIONS = 50;
|
|
3
|
+
export const questionOptionShape = {
|
|
4
|
+
id: z.string().min(1).max(100).describe("Stable id: keep it when you change the label, so answers, conditions and flags that use it stay."),
|
|
5
|
+
label: z.string().min(1).max(200),
|
|
6
|
+
};
|
|
7
|
+
export const questionConditionShape = {
|
|
8
|
+
fieldId: z.string().min(1).describe("id of a custom yes_no, single_choice or multi_choice question earlier in the list."),
|
|
9
|
+
anyOf: z.array(z.string().min(1)).min(1).describe('Option ids of that question ("yes" / "no" for yes_no). Any one selected shows this field.'),
|
|
10
|
+
};
|
|
11
|
+
export const questionnaireFieldShape = {
|
|
12
|
+
id: z.string().min(1).describe('Keep the id of a field you keep. For a new field, pick a new unique id, e.g. "custom-esg-policy".'),
|
|
13
|
+
kind: z.enum(["standard", "custom"]).describe("standard: a document Pleaxy knows (docType); custom: a question."),
|
|
14
|
+
docType: z.enum(["w9", "w8ben", "coi", "bank_letter", "business_license"]).optional().describe("Required when kind is standard."),
|
|
15
|
+
label: z.string().min(1),
|
|
16
|
+
required: z.boolean(),
|
|
17
|
+
helpText: z.string().optional(),
|
|
18
|
+
type: z
|
|
19
|
+
.enum(["text", "long_text", "yes_no", "single_choice", "multi_choice", "number", "date", "file"])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Custom questions only: how it's answered. Absent reads as text. file: the supplier uploads a file."),
|
|
22
|
+
options: z.array(z.object(questionOptionShape)).min(2).max(MAX_OPTIONS).optional().describe("single_choice and multi_choice only."),
|
|
23
|
+
showIf: z.object(questionConditionShape).optional().describe("Ask this only after given answers to an earlier choice question (a follow-up). Absent: always asked."),
|
|
24
|
+
section: z
|
|
25
|
+
.string()
|
|
26
|
+
.max(120)
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("The heading the field sits under. Consecutive fields with the same section show together; give every field in a section the same text."),
|
|
29
|
+
flagIf: z
|
|
30
|
+
.array(z.string().min(1))
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Choice questions only: option ids that flag the response for the buyer's review. Suppliers never see this."),
|
|
33
|
+
};
|
|
34
|
+
export const saveQuestionnaireShape = {
|
|
35
|
+
fields: z.array(z.object(questionnaireFieldShape)).describe("The whole questionnaire, in order. Any field left out is removed."),
|
|
36
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const date = (what) => z
|
|
3
|
+
.string()
|
|
4
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
5
|
+
.describe(what);
|
|
6
|
+
export const receiptLineShape = {
|
|
7
|
+
lineNo: z.number().int().min(1).describe("The PO line, by its lineNo on the order."),
|
|
8
|
+
qty: z.number().min(0).optional().describe("For a line ordered by quantity: how many, in the line's unit (hours, days, units)."),
|
|
9
|
+
amount: z.number().min(0).optional().describe("For a line ordered by amount (not to exceed): the amount received, before tax, in the order's currency."),
|
|
10
|
+
};
|
|
11
|
+
export const createReceiptShape = {
|
|
12
|
+
receivedAt: date("The day the goods arrived or the work was done up to (YYYY-MM-DD), in the workspace's time zone. Not in the future."),
|
|
13
|
+
periodFrom: date("For work: the first day of the period it covers.").optional(),
|
|
14
|
+
periodTo: date("For work: the last day of the period it covers.").optional(),
|
|
15
|
+
lines: z
|
|
16
|
+
.array(z.object(receiptLineShape))
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(500)
|
|
19
|
+
.describe("What was received, per PO line. Goods lines and service lines go on separate receipts: a goods receipt for goods, a service entry sheet for services."),
|
|
20
|
+
note: z.string().max(1000).optional().describe("A note, e.g. what the timesheet covers."),
|
|
21
|
+
};
|
|
22
|
+
export const returnReceiptShape = {
|
|
23
|
+
returnedAt: date("The day the goods were sent back (YYYY-MM-DD). Not in the future."),
|
|
24
|
+
lines: z
|
|
25
|
+
.array(z.object(receiptLineShape))
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(500)
|
|
28
|
+
.describe("What goes back, per line of the receipt: qty on a line ordered by quantity, amount on one ordered by amount."),
|
|
29
|
+
reason: z.string().min(1).max(500).describe("Why, e.g. damaged on arrival. The supplier sees it; use the user's own words."),
|
|
30
|
+
want: z.enum(["replacement", "credit_note", "none"]).describe("What the buyer asks the supplier for."),
|
|
31
|
+
};
|
|
32
|
+
export const rejectReceiptShape = {
|
|
33
|
+
reason: z.string().min(1).max(500).describe("Why it is rejected. The supplier sees it word for word, so it should say what to correct; use the user's own words."),
|
|
34
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const REJECTION_REASON_CODES = ["amount_incorrect", "not_ordered_or_not_received", "duplicate", "missing_or_wrong_po", "wrong_billing_entity", "other"];
|
|
3
|
+
export const rejectInvoiceShape = {
|
|
4
|
+
reasonCode: z.enum(REJECTION_REASON_CODES).describe("Why the invoice is rejected. The supplier sees its label."),
|
|
5
|
+
reason: z
|
|
6
|
+
.string()
|
|
7
|
+
.min(10)
|
|
8
|
+
.max(500)
|
|
9
|
+
.describe("Up to 500 characters with at least 10 letters or numbers, shown to the supplier word for word (line breaks become spaces): what they should fix before sending a corrected invoice. Use the user's own words; no personal details or internal comments."),
|
|
10
|
+
};
|
|
11
|
+
const WHITESPACE = /[\t\n\v\f\r \u00a0\u0085\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]/g;
|
|
12
|
+
const NOT_VISIBLE = /[^\p{L}\p{M}\p{N}\p{P}\p{S} ]|[\p{Default_Ignorable_Code_Point}\u2800\u2d7f\u303f\u{16fe4}\u{1d159}]/gu;
|
|
13
|
+
const MARK_RUN = /\p{M}+/gu;
|
|
14
|
+
const SPACING_MARK = /^\p{Mc}$/u;
|
|
15
|
+
const MAX_NONSPACING_MARKS = 4;
|
|
16
|
+
function capMarks(run) {
|
|
17
|
+
let nonspacing = 0;
|
|
18
|
+
return [...run].filter((ch) => SPACING_MARK.test(ch) || ++nonspacing <= MAX_NONSPACING_MARKS).join("");
|
|
19
|
+
}
|
|
20
|
+
export function cleanRejectionNote(text) {
|
|
21
|
+
return text.normalize("NFC").replace(WHITESPACE, " ").replace(NOT_VISIBLE, "").replace(MARK_RUN, capMarks).replace(/ {2,}/g, " ").trim();
|
|
22
|
+
}
|
|
23
|
+
export function quoteRejectionNotes(data) {
|
|
24
|
+
if (Array.isArray(data))
|
|
25
|
+
return data.map(quoteRejectionNotes);
|
|
26
|
+
if (!data || typeof data !== "object")
|
|
27
|
+
return data;
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [key, value] of Object.entries(data)) {
|
|
30
|
+
const rejection = key === "rejection" && value && typeof value === "object" ? value : undefined;
|
|
31
|
+
out[key] =
|
|
32
|
+
rejection && typeof rejection.reason === "string"
|
|
33
|
+
? { ...rejection, reason: `<buyer_rejection_note>${cleanRejectionNote(rejection.reason).replace(/</g, "<").replace(/>/g, ">")}</buyer_rejection_note>` }
|
|
34
|
+
: quoteRejectionNotes(value);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|