@casys/mcp-erpnext 3.1.0 → 3.2.0-beta.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/README.md +137 -70
- package/THIRD_PARTY_NOTICES.txt +58 -992
- package/mcp-erpnext.mjs +3264 -168
- package/package.json +4 -4
- package/ui-dist/buy-evidence-viewer/index.html +2562 -0
- package/ui-dist/chart-viewer/index.html +58 -99
- package/ui-dist/doc-viewer/index.html +116 -0
- package/ui-dist/doclist-viewer/index.html +44 -82
- package/ui-dist/funnel-viewer/index.html +42 -41
- package/ui-dist/invoice-viewer/index.html +42 -86
- package/ui-dist/kanban-viewer/index.html +42 -80
- package/ui-dist/kpi-viewer/index.html +42 -41
- package/ui-dist/stock-viewer/index.html +42 -80
package/mcp-erpnext.mjs
CHANGED
|
@@ -1784,14 +1784,14 @@ var require_code3 = __commonJS({
|
|
|
1784
1784
|
}
|
|
1785
1785
|
exports.callValidateCode = callValidateCode;
|
|
1786
1786
|
var newRegExp = (0, codegen_1._)`new RegExp`;
|
|
1787
|
-
function usePattern({ gen, it: { opts } },
|
|
1787
|
+
function usePattern({ gen, it: { opts } }, pattern2) {
|
|
1788
1788
|
const u = opts.unicodeRegExp ? "u" : "";
|
|
1789
1789
|
const { regExp } = opts.code;
|
|
1790
|
-
const rx = regExp(
|
|
1790
|
+
const rx = regExp(pattern2, u);
|
|
1791
1791
|
return gen.scopeValue("pattern", {
|
|
1792
1792
|
key: rx.toString(),
|
|
1793
1793
|
ref: rx,
|
|
1794
|
-
code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${
|
|
1794
|
+
code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern2}, ${u})`
|
|
1795
1795
|
});
|
|
1796
1796
|
}
|
|
1797
1797
|
exports.usePattern = usePattern;
|
|
@@ -6902,7 +6902,7 @@ var require_discriminator2 = __commonJS({
|
|
|
6902
6902
|
error: error2,
|
|
6903
6903
|
code(cxt) {
|
|
6904
6904
|
const { gen, data, schema, parentSchema, it } = cxt;
|
|
6905
|
-
const { oneOf } = parentSchema;
|
|
6905
|
+
const { oneOf: oneOf2 } = parentSchema;
|
|
6906
6906
|
if (!it.opts.discriminator) {
|
|
6907
6907
|
throw new Error("discriminator: requires discriminator option");
|
|
6908
6908
|
}
|
|
@@ -6911,7 +6911,7 @@ var require_discriminator2 = __commonJS({
|
|
|
6911
6911
|
throw new Error("discriminator: requires propertyName");
|
|
6912
6912
|
if (schema.mapping)
|
|
6913
6913
|
throw new Error("discriminator: mapping is not supported");
|
|
6914
|
-
if (!
|
|
6914
|
+
if (!oneOf2)
|
|
6915
6915
|
throw new Error("discriminator: requires oneOf keyword");
|
|
6916
6916
|
const valid = gen.let("valid", false);
|
|
6917
6917
|
const tag2 = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
|
|
@@ -6939,8 +6939,8 @@ var require_discriminator2 = __commonJS({
|
|
|
6939
6939
|
const oneOfMapping = {};
|
|
6940
6940
|
const topRequired = hasRequired(parentSchema);
|
|
6941
6941
|
let tagRequired = true;
|
|
6942
|
-
for (let i = 0; i <
|
|
6943
|
-
let sch =
|
|
6942
|
+
for (let i = 0; i < oneOf2.length; i++) {
|
|
6943
|
+
let sch = oneOf2[i];
|
|
6944
6944
|
if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
|
|
6945
6945
|
const ref = sch.$ref;
|
|
6946
6946
|
sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
|
|
@@ -9022,9 +9022,9 @@ ${indent}`) + "'";
|
|
|
9022
9022
|
return quotedString(value, ctx);
|
|
9023
9023
|
}
|
|
9024
9024
|
const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : "");
|
|
9025
|
-
const
|
|
9025
|
+
const literal3 = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
|
|
9026
9026
|
if (!value)
|
|
9027
|
-
return
|
|
9027
|
+
return literal3 ? "|\n" : ">\n";
|
|
9028
9028
|
let chomp;
|
|
9029
9029
|
let endStart;
|
|
9030
9030
|
for (endStart = value.length; endStart > 0; --endStart) {
|
|
@@ -9073,7 +9073,7 @@ ${indent}`) + "'";
|
|
|
9073
9073
|
if (onComment)
|
|
9074
9074
|
onComment();
|
|
9075
9075
|
}
|
|
9076
|
-
if (!
|
|
9076
|
+
if (!literal3) {
|
|
9077
9077
|
const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`);
|
|
9078
9078
|
let literalFallback = false;
|
|
9079
9079
|
const foldOptions = getFoldOptions(ctx, true);
|
|
@@ -16837,8 +16837,8 @@ var $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (ins
|
|
|
16837
16837
|
var $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
|
|
16838
16838
|
$ZodCheck.init(inst, def);
|
|
16839
16839
|
const escapedRegex = escapeRegex(def.includes);
|
|
16840
|
-
const
|
|
16841
|
-
def.pattern =
|
|
16840
|
+
const pattern2 = new RegExp(typeof def.position === "number" ? `^.{${def.position},}${escapedRegex}` : escapedRegex);
|
|
16841
|
+
def.pattern = pattern2;
|
|
16842
16842
|
inst._zod.check = (payload) => {
|
|
16843
16843
|
if (payload.value.includes(def.includes, def.position))
|
|
16844
16844
|
return;
|
|
@@ -16855,8 +16855,8 @@ var $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst,
|
|
|
16855
16855
|
});
|
|
16856
16856
|
var $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
|
|
16857
16857
|
$ZodCheck.init(inst, def);
|
|
16858
|
-
const
|
|
16859
|
-
def.pattern ?? (def.pattern =
|
|
16858
|
+
const pattern2 = new RegExp(`^${escapeRegex(def.prefix)}.*`);
|
|
16859
|
+
def.pattern ?? (def.pattern = pattern2);
|
|
16860
16860
|
inst._zod.check = (payload) => {
|
|
16861
16861
|
if (payload.value.startsWith(def.prefix))
|
|
16862
16862
|
return;
|
|
@@ -16873,8 +16873,8 @@ var $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (i
|
|
|
16873
16873
|
});
|
|
16874
16874
|
var $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
|
|
16875
16875
|
$ZodCheck.init(inst, def);
|
|
16876
|
-
const
|
|
16877
|
-
def.pattern ?? (def.pattern =
|
|
16876
|
+
const pattern2 = new RegExp(`.*${escapeRegex(def.suffix)}$`);
|
|
16877
|
+
def.pattern ?? (def.pattern = pattern2);
|
|
16878
16878
|
inst._zod.check = (payload) => {
|
|
16879
16879
|
if (payload.value.endsWith(def.suffix))
|
|
16880
16880
|
return;
|
|
@@ -18435,8 +18435,8 @@ var $ZodOptional = /* @__PURE__ */ $constructor("$ZodOptional", (inst, def) => {
|
|
|
18435
18435
|
return values ? /* @__PURE__ */ new Set([...values, void 0]) : void 0;
|
|
18436
18436
|
});
|
|
18437
18437
|
defineLazyInternal(inst, "pattern", (zod) => {
|
|
18438
|
-
const
|
|
18439
|
-
return
|
|
18438
|
+
const pattern2 = zod.def.innerType._zod.pattern;
|
|
18439
|
+
return pattern2 ? new RegExp(`^(${cleanRegex(pattern2.source)})?$`) : void 0;
|
|
18440
18440
|
});
|
|
18441
18441
|
inst._zod.parse = (payload, ctx) => {
|
|
18442
18442
|
if (payload.value === void 0) {
|
|
@@ -18463,8 +18463,8 @@ var $ZodNullable = /* @__PURE__ */ $constructor("$ZodNullable", (inst, def) => {
|
|
|
18463
18463
|
defineLazyInternal(inst, "optin", (zod) => zod.def.innerType._zod.optin);
|
|
18464
18464
|
defineLazyInternal(inst, "optout", (zod) => zod.def.innerType._zod.optout);
|
|
18465
18465
|
defineLazyInternal(inst, "pattern", (zod) => {
|
|
18466
|
-
const
|
|
18467
|
-
return
|
|
18466
|
+
const pattern2 = zod.def.innerType._zod.pattern;
|
|
18467
|
+
return pattern2 ? new RegExp(`^(${cleanRegex(pattern2.source)}|null)$`) : void 0;
|
|
18468
18468
|
});
|
|
18469
18469
|
defineLazyInternal(inst, "values", (zod) => {
|
|
18470
18470
|
return zod.def.innerType._zod.values ? /* @__PURE__ */ new Set([...zod.def.innerType._zod.values, null]) : void 0;
|
|
@@ -19545,12 +19545,12 @@ function _length(length, params) {
|
|
|
19545
19545
|
});
|
|
19546
19546
|
}
|
|
19547
19547
|
// @__NO_SIDE_EFFECTS__
|
|
19548
|
-
function _regex(
|
|
19548
|
+
function _regex(pattern2, params) {
|
|
19549
19549
|
return new $ZodCheckRegex({
|
|
19550
19550
|
check: "string_format",
|
|
19551
19551
|
format: "regex",
|
|
19552
19552
|
...normalizeParams(params),
|
|
19553
|
-
pattern
|
|
19553
|
+
pattern: pattern2
|
|
19554
19554
|
});
|
|
19555
19555
|
}
|
|
19556
19556
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -20236,9 +20236,9 @@ var addDivisor = (agg, value) => {
|
|
|
20236
20236
|
if (!agg.multipleOf.includes(value))
|
|
20237
20237
|
agg.multipleOf.push(value);
|
|
20238
20238
|
};
|
|
20239
|
-
var addPattern = (agg,
|
|
20239
|
+
var addPattern = (agg, pattern2) => {
|
|
20240
20240
|
agg.patterns ?? (agg.patterns = /* @__PURE__ */ new Set());
|
|
20241
|
-
agg.patterns.add(
|
|
20241
|
+
agg.patterns.add(pattern2);
|
|
20242
20242
|
};
|
|
20243
20243
|
var intersectMime = (agg, mime) => {
|
|
20244
20244
|
agg.mime = agg.mime ? agg.mime.filter((m) => mime.includes(m)) : [...mime];
|
|
@@ -20303,8 +20303,8 @@ function aggregateChecks(schema) {
|
|
|
20303
20303
|
}
|
|
20304
20304
|
if (bag.mime)
|
|
20305
20305
|
intersectMime(agg, bag.mime);
|
|
20306
|
-
for (const
|
|
20307
|
-
addPattern(agg,
|
|
20306
|
+
for (const pattern2 of bag.patterns ?? [])
|
|
20307
|
+
addPattern(agg, pattern2);
|
|
20308
20308
|
return agg;
|
|
20309
20309
|
}
|
|
20310
20310
|
var formatMap = {
|
|
@@ -20486,11 +20486,11 @@ var nanProcessor = (schema, ctx, json, params) => {
|
|
|
20486
20486
|
};
|
|
20487
20487
|
var templateLiteralProcessor = (schema, _ctx, json, _params) => {
|
|
20488
20488
|
const _json = json;
|
|
20489
|
-
const
|
|
20490
|
-
if (!
|
|
20489
|
+
const pattern2 = schema._zod.pattern;
|
|
20490
|
+
if (!pattern2)
|
|
20491
20491
|
throw new Error("Pattern not found in template literal");
|
|
20492
20492
|
_json.type = "string";
|
|
20493
|
-
_json.pattern =
|
|
20493
|
+
_json.pattern = pattern2.source;
|
|
20494
20494
|
};
|
|
20495
20495
|
var fileProcessor = (schema, _ctx, json, _params) => {
|
|
20496
20496
|
const _json = json;
|
|
@@ -20730,8 +20730,8 @@ function rewriteKeyNames(ctx) {
|
|
|
20730
20730
|
bySchema.set(entry.schema, entry);
|
|
20731
20731
|
}
|
|
20732
20732
|
const rewrites = /* @__PURE__ */ new Map();
|
|
20733
|
-
for (const
|
|
20734
|
-
const seen = ctx.seen.get(
|
|
20733
|
+
for (const record3 of pendingRecords.get(ctx) ?? []) {
|
|
20734
|
+
const seen = ctx.seen.get(record3);
|
|
20735
20735
|
const names = (seen?.def ?? seen?.schema)?.propertyNames;
|
|
20736
20736
|
if (!names || names === true || rewrites.has(names))
|
|
20737
20737
|
continue;
|
|
@@ -20761,8 +20761,8 @@ var recordProcessor = (schema, ctx, _json, params) => {
|
|
|
20761
20761
|
path: [...params.path, "patternProperties", "*"]
|
|
20762
20762
|
});
|
|
20763
20763
|
json.patternProperties = {};
|
|
20764
|
-
for (const
|
|
20765
|
-
assignProp(json.patternProperties, exactPattern(
|
|
20764
|
+
for (const pattern2 of patterns) {
|
|
20765
|
+
assignProp(json.patternProperties, exactPattern(pattern2).source, valueSchema);
|
|
20766
20766
|
}
|
|
20767
20767
|
} else {
|
|
20768
20768
|
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
|
|
@@ -26313,8 +26313,8 @@ function zodEmittedPattern(schema) {
|
|
|
26313
26313
|
return typeof jsonSchema.pattern === "string" ? jsonSchema.pattern : void 0;
|
|
26314
26314
|
}
|
|
26315
26315
|
var DATETIME_FRACTION_DIGITS = /\\\.\\d\{(\d+)\}/;
|
|
26316
|
-
function datetimeReferenceSchemas(
|
|
26317
|
-
const fractionDigits = DATETIME_FRACTION_DIGITS.exec(
|
|
26316
|
+
function datetimeReferenceSchemas(pattern2) {
|
|
26317
|
+
const fractionDigits = DATETIME_FRACTION_DIGITS.exec(pattern2);
|
|
26318
26318
|
const precisions = [
|
|
26319
26319
|
void 0,
|
|
26320
26320
|
-1,
|
|
@@ -26327,7 +26327,7 @@ function datetimeReferenceSchemas(pattern) {
|
|
|
26327
26327
|
precision
|
|
26328
26328
|
}))));
|
|
26329
26329
|
}
|
|
26330
|
-
function referencePatternsForFormat(format,
|
|
26330
|
+
function referencePatternsForFormat(format, pattern2) {
|
|
26331
26331
|
let referenceSchemas;
|
|
26332
26332
|
switch (format) {
|
|
26333
26333
|
case "email":
|
|
@@ -26340,14 +26340,14 @@ function referencePatternsForFormat(format, pattern) {
|
|
|
26340
26340
|
referenceSchemas = [iso_exports.date()];
|
|
26341
26341
|
break;
|
|
26342
26342
|
case "date-time":
|
|
26343
|
-
referenceSchemas = datetimeReferenceSchemas(
|
|
26343
|
+
referenceSchemas = datetimeReferenceSchemas(pattern2);
|
|
26344
26344
|
break;
|
|
26345
26345
|
}
|
|
26346
26346
|
return new Set(referenceSchemas.map((schema) => zodEmittedPattern(schema)).filter((emitted) => emitted !== void 0));
|
|
26347
26347
|
}
|
|
26348
|
-
function isLibraryFormatPattern(format,
|
|
26348
|
+
function isLibraryFormatPattern(format, pattern2, vendor) {
|
|
26349
26349
|
if (vendor !== "zod") return true;
|
|
26350
|
-
return referencePatternsForFormat(format,
|
|
26350
|
+
return referencePatternsForFormat(format, pattern2).has(pattern2);
|
|
26351
26351
|
}
|
|
26352
26352
|
function promptArgumentsFromStandardSchema(schema) {
|
|
26353
26353
|
const jsonSchema = standardSchemaToJsonSchema(schema, "input");
|
|
@@ -29295,14 +29295,14 @@ var require_code = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
29295
29295
|
}
|
|
29296
29296
|
exports.callValidateCode = callValidateCode;
|
|
29297
29297
|
const newRegExp = (0, codegen_1._)`new RegExp`;
|
|
29298
|
-
function usePattern({ gen, it: { opts } },
|
|
29298
|
+
function usePattern({ gen, it: { opts } }, pattern2) {
|
|
29299
29299
|
const u = opts.unicodeRegExp ? "u" : "";
|
|
29300
29300
|
const { regExp } = opts.code;
|
|
29301
|
-
const rx = regExp(
|
|
29301
|
+
const rx = regExp(pattern2, u);
|
|
29302
29302
|
return gen.scopeValue("pattern", {
|
|
29303
29303
|
key: rx.toString(),
|
|
29304
29304
|
ref: rx,
|
|
29305
|
-
code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${
|
|
29305
|
+
code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern2}, ${u})`
|
|
29306
29306
|
});
|
|
29307
29307
|
}
|
|
29308
29308
|
exports.usePattern = usePattern;
|
|
@@ -32965,12 +32965,12 @@ var require_discriminator = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
32965
32965
|
},
|
|
32966
32966
|
code(cxt) {
|
|
32967
32967
|
const { gen, data, schema, parentSchema, it } = cxt;
|
|
32968
|
-
const { oneOf } = parentSchema;
|
|
32968
|
+
const { oneOf: oneOf2 } = parentSchema;
|
|
32969
32969
|
if (!it.opts.discriminator) throw new Error("discriminator: requires discriminator option");
|
|
32970
32970
|
const tagName = schema.propertyName;
|
|
32971
32971
|
if (typeof tagName != "string") throw new Error("discriminator: requires propertyName");
|
|
32972
32972
|
if (schema.mapping) throw new Error("discriminator: mapping is not supported");
|
|
32973
|
-
if (!
|
|
32973
|
+
if (!oneOf2) throw new Error("discriminator: requires oneOf keyword");
|
|
32974
32974
|
const valid = gen.let("valid", false);
|
|
32975
32975
|
const tag2 = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
|
|
32976
32976
|
gen.if((0, codegen_1._)`typeof ${tag2} == "string"`, () => validateMapping(), () => cxt.error(false, {
|
|
@@ -33008,8 +33008,8 @@ var require_discriminator = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
33008
33008
|
const oneOfMapping = {};
|
|
33009
33009
|
const topRequired = hasRequired(parentSchema);
|
|
33010
33010
|
let tagRequired = true;
|
|
33011
|
-
for (let i = 0; i <
|
|
33012
|
-
let sch =
|
|
33011
|
+
for (let i = 0; i < oneOf2.length; i++) {
|
|
33012
|
+
let sch = oneOf2[i];
|
|
33013
33013
|
if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
|
|
33014
33014
|
const ref = sch.$ref;
|
|
33015
33015
|
sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
|
|
@@ -39676,7 +39676,7 @@ var Node = class Node2 {
|
|
|
39676
39676
|
let node2 = this;
|
|
39677
39677
|
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
39678
39678
|
const token = tokens[i];
|
|
39679
|
-
const
|
|
39679
|
+
const pattern2 = token.length === 1 ? token === "*" ? i === len - 1 ? [
|
|
39680
39680
|
"",
|
|
39681
39681
|
"",
|
|
39682
39682
|
".*"
|
|
@@ -39690,10 +39690,10 @@ var Node = class Node2 {
|
|
|
39690
39690
|
TAIL_WILDCARD_REG_EXP_STR
|
|
39691
39691
|
] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
|
|
39692
39692
|
let nextNode;
|
|
39693
|
-
if (
|
|
39694
|
-
const name =
|
|
39695
|
-
let regexpStr =
|
|
39696
|
-
if (name &&
|
|
39693
|
+
if (pattern2) {
|
|
39694
|
+
const name = pattern2[1];
|
|
39695
|
+
let regexpStr = pattern2[2] || "[^/]+";
|
|
39696
|
+
if (name && pattern2[2]) {
|
|
39697
39697
|
if (regexpStr === ".*") throw PATH_ERROR;
|
|
39698
39698
|
regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
|
|
39699
39699
|
if (/\((?!\?:)/.test(regexpStr)) throw PATH_ERROR;
|
|
@@ -39957,16 +39957,16 @@ var Node3 = class Node4 {
|
|
|
39957
39957
|
let i = 0;
|
|
39958
39958
|
for (const p of parts) {
|
|
39959
39959
|
const nextP = parts[++i];
|
|
39960
|
-
const
|
|
39961
|
-
const isParam = Array.isArray(
|
|
39962
|
-
const key = isParam ?
|
|
39960
|
+
const pattern2 = getPattern(p, nextP) || (nextP === void 0 && p && p.indexOf("*") === p.length - 1 ? p : null);
|
|
39961
|
+
const isParam = Array.isArray(pattern2);
|
|
39962
|
+
const key = isParam ? pattern2[0] : pattern2 || p;
|
|
39963
39963
|
const child = curNode.#children[key] ||= new Node4();
|
|
39964
|
-
if (
|
|
39965
|
-
child.#pattern =
|
|
39964
|
+
if (pattern2 && !child.#pattern) {
|
|
39965
|
+
child.#pattern = pattern2;
|
|
39966
39966
|
curNode.#patterns.push(child);
|
|
39967
39967
|
}
|
|
39968
39968
|
curNode = child;
|
|
39969
|
-
if (isParam) possibleKeys.add(
|
|
39969
|
+
if (isParam) possibleKeys.add(pattern2[1]);
|
|
39970
39970
|
}
|
|
39971
39971
|
curNode.#methods.push({ [method]: {
|
|
39972
39972
|
handler,
|
|
@@ -40011,19 +40011,19 @@ var Node3 = class Node4 {
|
|
|
40011
40011
|
} else tempNodes.push(nextNode);
|
|
40012
40012
|
}
|
|
40013
40013
|
for (const child of node2.#patterns) {
|
|
40014
|
-
const
|
|
40014
|
+
const pattern2 = child.#pattern;
|
|
40015
40015
|
const params = node2.#params === emptyParams ? {} : { ...node2.#params };
|
|
40016
|
-
if (typeof
|
|
40017
|
-
if (
|
|
40016
|
+
if (typeof pattern2 === "string") {
|
|
40017
|
+
if (pattern2 === "*" || part.startsWith(pattern2.slice(0, -1))) {
|
|
40018
40018
|
this.#pushHandlerSets(handlerSets, child, method, node2.#params);
|
|
40019
|
-
if (
|
|
40019
|
+
if (pattern2 === "*") {
|
|
40020
40020
|
child.#params = params;
|
|
40021
40021
|
tempNodes.push(child);
|
|
40022
40022
|
}
|
|
40023
40023
|
}
|
|
40024
40024
|
continue;
|
|
40025
40025
|
}
|
|
40026
|
-
const [, name, matcher] =
|
|
40026
|
+
const [, name, matcher] = pattern2;
|
|
40027
40027
|
if (!part && matcher === true) continue;
|
|
40028
40028
|
if (matcher !== true) {
|
|
40029
40029
|
if (!partOffsets) {
|
|
@@ -48086,6 +48086,115 @@ function stableStringify(value) {
|
|
|
48086
48086
|
var DEFAULT_RETRY_STATUSES = [408, 429, 502, 503, 504];
|
|
48087
48087
|
var DEFAULT_RETRY_METHODS = ["GET"];
|
|
48088
48088
|
var DEFAULT_MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
48089
|
+
var DEFAULT_MAX_DOWNLOAD_BYTES = 10 * 1024 * 1024;
|
|
48090
|
+
var MAX_ERROR_BODY_BYTES = 64 * 1024;
|
|
48091
|
+
var ResponseBodyLimitError = class extends Error {
|
|
48092
|
+
constructor(label, size, limit) {
|
|
48093
|
+
super(
|
|
48094
|
+
`[FrappeClient] ${label} size ${size} bytes exceeds the ${limit}-byte limit`
|
|
48095
|
+
);
|
|
48096
|
+
this.name = "ResponseBodyLimitError";
|
|
48097
|
+
}
|
|
48098
|
+
};
|
|
48099
|
+
async function readResponseBytes(response, maxBytes, label) {
|
|
48100
|
+
const declaredLength = response.headers.get("content-length")?.trim();
|
|
48101
|
+
if (declaredLength && /^\d+$/.test(declaredLength)) {
|
|
48102
|
+
const size = Number(declaredLength);
|
|
48103
|
+
if (size > maxBytes) {
|
|
48104
|
+
try {
|
|
48105
|
+
await response.body?.cancel();
|
|
48106
|
+
} catch {
|
|
48107
|
+
}
|
|
48108
|
+
throw new ResponseBodyLimitError(label, size, maxBytes);
|
|
48109
|
+
}
|
|
48110
|
+
}
|
|
48111
|
+
if (!response.body) return new Uint8Array();
|
|
48112
|
+
const reader = response.body.getReader();
|
|
48113
|
+
const chunks = [];
|
|
48114
|
+
let total = 0;
|
|
48115
|
+
try {
|
|
48116
|
+
while (true) {
|
|
48117
|
+
const { done, value } = await reader.read();
|
|
48118
|
+
if (done) break;
|
|
48119
|
+
total += value.byteLength;
|
|
48120
|
+
if (total > maxBytes) {
|
|
48121
|
+
try {
|
|
48122
|
+
await reader.cancel();
|
|
48123
|
+
} catch {
|
|
48124
|
+
}
|
|
48125
|
+
throw new ResponseBodyLimitError(label, total, maxBytes);
|
|
48126
|
+
}
|
|
48127
|
+
chunks.push(value);
|
|
48128
|
+
}
|
|
48129
|
+
} finally {
|
|
48130
|
+
reader.releaseLock();
|
|
48131
|
+
}
|
|
48132
|
+
const bytes = new Uint8Array(total);
|
|
48133
|
+
let offset = 0;
|
|
48134
|
+
for (const chunk of chunks) {
|
|
48135
|
+
bytes.set(chunk, offset);
|
|
48136
|
+
offset += chunk.byteLength;
|
|
48137
|
+
}
|
|
48138
|
+
return bytes;
|
|
48139
|
+
}
|
|
48140
|
+
function isUnsafeControl(character) {
|
|
48141
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
48142
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
|
|
48143
|
+
}
|
|
48144
|
+
function validateLocalFileUrl(fileUrl, isPrivate) {
|
|
48145
|
+
if (typeof fileUrl !== "string" || fileUrl.length === 0) {
|
|
48146
|
+
throw new Error("[FrappeClient] File.file_url must be a non-empty string");
|
|
48147
|
+
}
|
|
48148
|
+
if (fileUrl !== fileUrl.trim()) {
|
|
48149
|
+
throw new Error(
|
|
48150
|
+
"[FrappeClient] File.file_url must not contain whitespace padding"
|
|
48151
|
+
);
|
|
48152
|
+
}
|
|
48153
|
+
const expectedPrefix = isPrivate ? "/private/files/" : "/files/";
|
|
48154
|
+
let decoded = fileUrl;
|
|
48155
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
48156
|
+
if (Array.from(decoded).some(isUnsafeControl) || decoded.includes("\\") || decoded.includes("?") || decoded.includes("#")) {
|
|
48157
|
+
throw new Error(
|
|
48158
|
+
"[FrappeClient] File.file_url must be a local Frappe file path without query, fragment, NUL, or backslash"
|
|
48159
|
+
);
|
|
48160
|
+
}
|
|
48161
|
+
if (!decoded.startsWith(expectedPrefix)) {
|
|
48162
|
+
throw new Error(
|
|
48163
|
+
`[FrappeClient] File.file_url does not match is_private=${isPrivate ? 1 : 0}`
|
|
48164
|
+
);
|
|
48165
|
+
}
|
|
48166
|
+
const fileSegment = decoded.slice(expectedPrefix.length);
|
|
48167
|
+
if (!fileSegment || fileSegment === "." || fileSegment === ".." || fileSegment.includes("/")) {
|
|
48168
|
+
throw new Error(
|
|
48169
|
+
"[FrappeClient] File.file_url must contain exactly one safe file segment"
|
|
48170
|
+
);
|
|
48171
|
+
}
|
|
48172
|
+
let next;
|
|
48173
|
+
try {
|
|
48174
|
+
next = decodeURIComponent(decoded);
|
|
48175
|
+
} catch {
|
|
48176
|
+
throw new Error(
|
|
48177
|
+
"[FrappeClient] File.file_url contains malformed percent encoding"
|
|
48178
|
+
);
|
|
48179
|
+
}
|
|
48180
|
+
if (next === decoded) return fileUrl;
|
|
48181
|
+
decoded = next;
|
|
48182
|
+
}
|
|
48183
|
+
throw new Error(
|
|
48184
|
+
"[FrappeClient] File.file_url contains excessive nested percent encoding"
|
|
48185
|
+
);
|
|
48186
|
+
}
|
|
48187
|
+
function sanitizeDownloadFileName(raw2, fileId) {
|
|
48188
|
+
const fallbackId = fileId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80) || "file";
|
|
48189
|
+
if (typeof raw2 !== "string") return `erpnext-${fallbackId}`;
|
|
48190
|
+
const normalized = Array.from(raw2.normalize("NFC")).filter((character) => !isUnsafeControl(character)).join("").replace(/[\\/:*?"<>|]/g, "_").trim().replace(/[. ]+$/g, "");
|
|
48191
|
+
const bounded = Array.from(normalized).slice(0, 255).join("");
|
|
48192
|
+
return !bounded || bounded === "." || bounded === ".." ? `erpnext-${fallbackId}` : bounded;
|
|
48193
|
+
}
|
|
48194
|
+
function normalizeMimeType(raw2) {
|
|
48195
|
+
const candidate = (raw2 ?? "").split(";", 1)[0].trim().toLowerCase();
|
|
48196
|
+
return /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i.test(candidate) && candidate !== "application/unknown" ? candidate : "application/octet-stream";
|
|
48197
|
+
}
|
|
48089
48198
|
function decodeBase64File(contentBase64, maxBytes) {
|
|
48090
48199
|
if (contentBase64.length === 0) {
|
|
48091
48200
|
throw new Error("[FrappeClient] File content must not be empty");
|
|
@@ -48113,6 +48222,24 @@ function decodeBase64File(contentBase64, maxBytes) {
|
|
|
48113
48222
|
}
|
|
48114
48223
|
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
48115
48224
|
}
|
|
48225
|
+
function normalizeFrappeSiteUrl(baseUrl) {
|
|
48226
|
+
if (typeof baseUrl !== "string" || baseUrl.trim() === "") {
|
|
48227
|
+
throw new Error(
|
|
48228
|
+
"[FrappeClient] baseUrl is required to derive site identity"
|
|
48229
|
+
);
|
|
48230
|
+
}
|
|
48231
|
+
let url2;
|
|
48232
|
+
try {
|
|
48233
|
+
url2 = new URL(baseUrl);
|
|
48234
|
+
} catch {
|
|
48235
|
+
throw new Error("[FrappeClient] baseUrl must be an absolute URL");
|
|
48236
|
+
}
|
|
48237
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
48238
|
+
throw new Error("[FrappeClient] baseUrl must be http or https");
|
|
48239
|
+
}
|
|
48240
|
+
const path = url2.pathname.replace(/\/+$/, "");
|
|
48241
|
+
return `${url2.protocol}//${url2.host}${path}`;
|
|
48242
|
+
}
|
|
48116
48243
|
var FrappeAPIError = class extends Error {
|
|
48117
48244
|
/**
|
|
48118
48245
|
* @param message - Human-readable error description
|
|
@@ -48171,6 +48298,7 @@ var FrappeClient = class {
|
|
|
48171
48298
|
authHeader;
|
|
48172
48299
|
timeoutMs;
|
|
48173
48300
|
maxUploadBytes;
|
|
48301
|
+
maxDownloadBytes;
|
|
48174
48302
|
retries;
|
|
48175
48303
|
retryStatuses;
|
|
48176
48304
|
retryBackoffMs;
|
|
@@ -48186,12 +48314,29 @@ var FrappeClient = class {
|
|
|
48186
48314
|
"[FrappeClient] maxUploadBytes must be a positive integer"
|
|
48187
48315
|
);
|
|
48188
48316
|
}
|
|
48317
|
+
this.maxDownloadBytes = config2.maxDownloadBytes ?? DEFAULT_MAX_DOWNLOAD_BYTES;
|
|
48318
|
+
if (!Number.isInteger(this.maxDownloadBytes) || this.maxDownloadBytes <= 0) {
|
|
48319
|
+
throw new Error(
|
|
48320
|
+
"[FrappeClient] maxDownloadBytes must be a positive integer"
|
|
48321
|
+
);
|
|
48322
|
+
}
|
|
48189
48323
|
this.retries = config2.retries ?? 3;
|
|
48190
48324
|
this.retryStatuses = config2.retryStatuses ?? DEFAULT_RETRY_STATUSES;
|
|
48191
48325
|
this.retryBackoffMs = config2.retryBackoffMs ?? 200;
|
|
48192
48326
|
this.retryMethods = config2.retryMethods ?? DEFAULT_RETRY_METHODS;
|
|
48193
48327
|
this.cache = config2.cache ?? new MemoryCache();
|
|
48194
48328
|
}
|
|
48329
|
+
/**
|
|
48330
|
+
* Normalized site URL of this configured client: origin + site path, with
|
|
48331
|
+
* credentials, query, and fragment stripped, and without a trailing slash.
|
|
48332
|
+
*
|
|
48333
|
+
* This is the preimage of the Buy capture `sourceInstance.siteId` SHA-256.
|
|
48334
|
+
* It is not an ERP database UUID and must not be copied onto the wire when
|
|
48335
|
+
* the opaque site identifier is sufficient.
|
|
48336
|
+
*/
|
|
48337
|
+
normalizedSiteUrl() {
|
|
48338
|
+
return normalizeFrappeSiteUrl(this.baseUrl);
|
|
48339
|
+
}
|
|
48195
48340
|
// ── Private HTTP helpers ────────────────────────────────────────────────────
|
|
48196
48341
|
buildHeaders(includeJsonContentType = true) {
|
|
48197
48342
|
const headers = {
|
|
@@ -48300,6 +48445,152 @@ var FrappeClient = class {
|
|
|
48300
48445
|
}
|
|
48301
48446
|
return responseBody;
|
|
48302
48447
|
}
|
|
48448
|
+
buildSameOriginUrl(path) {
|
|
48449
|
+
if (!path.startsWith("/")) {
|
|
48450
|
+
throw new Error("[FrappeClient] Download path must be root-relative");
|
|
48451
|
+
}
|
|
48452
|
+
let base;
|
|
48453
|
+
let target;
|
|
48454
|
+
try {
|
|
48455
|
+
base = new URL(this.baseUrl);
|
|
48456
|
+
target = new URL(`${this.baseUrl}${path}`);
|
|
48457
|
+
} catch {
|
|
48458
|
+
throw new Error("[FrappeClient] baseUrl must be a valid absolute URL");
|
|
48459
|
+
}
|
|
48460
|
+
if (target.origin !== base.origin) {
|
|
48461
|
+
throw new Error("[FrappeClient] Refusing a cross-origin download");
|
|
48462
|
+
}
|
|
48463
|
+
return target.href;
|
|
48464
|
+
}
|
|
48465
|
+
async requestBinary(path) {
|
|
48466
|
+
let lastError;
|
|
48467
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
48468
|
+
try {
|
|
48469
|
+
return await this.requestBinaryOnce(path);
|
|
48470
|
+
} catch (err) {
|
|
48471
|
+
lastError = err;
|
|
48472
|
+
if (attempt === this.retries || !this.isRetryable(err, "GET")) {
|
|
48473
|
+
throw err;
|
|
48474
|
+
}
|
|
48475
|
+
const delay = this.computeBackoff(attempt, err);
|
|
48476
|
+
if (delay > 0) {
|
|
48477
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
48478
|
+
}
|
|
48479
|
+
}
|
|
48480
|
+
}
|
|
48481
|
+
throw lastError;
|
|
48482
|
+
}
|
|
48483
|
+
async requestBinaryOnce(path) {
|
|
48484
|
+
const url2 = this.buildSameOriginUrl(path);
|
|
48485
|
+
const controller = new AbortController();
|
|
48486
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
48487
|
+
let response;
|
|
48488
|
+
try {
|
|
48489
|
+
response = await fetch(url2, {
|
|
48490
|
+
method: "GET",
|
|
48491
|
+
headers: {
|
|
48492
|
+
"Authorization": this.authHeader,
|
|
48493
|
+
"Accept": "*/*",
|
|
48494
|
+
"Cache-Control": "no-store"
|
|
48495
|
+
},
|
|
48496
|
+
cache: "no-store",
|
|
48497
|
+
redirect: "manual",
|
|
48498
|
+
signal: controller.signal
|
|
48499
|
+
});
|
|
48500
|
+
} catch (err) {
|
|
48501
|
+
clearTimeout(timer);
|
|
48502
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
48503
|
+
throw new FrappeAPIError(
|
|
48504
|
+
`Request timed out after ${this.timeoutMs}ms: GET ${path}`,
|
|
48505
|
+
408,
|
|
48506
|
+
null
|
|
48507
|
+
);
|
|
48508
|
+
}
|
|
48509
|
+
throw new FrappeAPIError(
|
|
48510
|
+
`Network error on GET ${path}: ${err.message}`,
|
|
48511
|
+
0,
|
|
48512
|
+
null
|
|
48513
|
+
);
|
|
48514
|
+
}
|
|
48515
|
+
try {
|
|
48516
|
+
if (response.redirected || response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) {
|
|
48517
|
+
try {
|
|
48518
|
+
await response.body?.cancel();
|
|
48519
|
+
} catch {
|
|
48520
|
+
}
|
|
48521
|
+
throw new FrappeAPIError(
|
|
48522
|
+
`GET ${path} refused an HTTP redirect`,
|
|
48523
|
+
response.status,
|
|
48524
|
+
null
|
|
48525
|
+
);
|
|
48526
|
+
}
|
|
48527
|
+
if (!response.ok) {
|
|
48528
|
+
let rawText;
|
|
48529
|
+
try {
|
|
48530
|
+
const errorBytes = await readResponseBytes(
|
|
48531
|
+
response,
|
|
48532
|
+
MAX_ERROR_BODY_BYTES,
|
|
48533
|
+
"Error response body"
|
|
48534
|
+
);
|
|
48535
|
+
rawText = new TextDecoder().decode(errorBytes);
|
|
48536
|
+
} catch (err) {
|
|
48537
|
+
if (!(err instanceof ResponseBodyLimitError)) throw err;
|
|
48538
|
+
rawText = err.message;
|
|
48539
|
+
}
|
|
48540
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
48541
|
+
let responseBody = rawText;
|
|
48542
|
+
if (contentType.includes("application/json") && rawText.length > 0) {
|
|
48543
|
+
try {
|
|
48544
|
+
responseBody = JSON.parse(rawText);
|
|
48545
|
+
} catch {
|
|
48546
|
+
responseBody = rawText;
|
|
48547
|
+
}
|
|
48548
|
+
}
|
|
48549
|
+
let message2 = response.statusText;
|
|
48550
|
+
if (typeof responseBody === "object" && responseBody !== null) {
|
|
48551
|
+
const body = responseBody;
|
|
48552
|
+
const baseMessage = body.message ?? body.exc_type ?? response.statusText;
|
|
48553
|
+
const serverDetails = extractServerMessages(body._server_messages);
|
|
48554
|
+
message2 = serverDetails ? `${baseMessage}: ${serverDetails}` : baseMessage;
|
|
48555
|
+
} else if (typeof responseBody === "string" && responseBody.length > 0) {
|
|
48556
|
+
message2 = responseBody.slice(0, 200);
|
|
48557
|
+
}
|
|
48558
|
+
throw new FrappeAPIError(
|
|
48559
|
+
`GET ${path} failed: ${message2}`,
|
|
48560
|
+
response.status,
|
|
48561
|
+
responseBody,
|
|
48562
|
+
parseRetryAfter(response.headers.get("retry-after"))
|
|
48563
|
+
);
|
|
48564
|
+
}
|
|
48565
|
+
const bytes = await readResponseBytes(
|
|
48566
|
+
response,
|
|
48567
|
+
this.maxDownloadBytes,
|
|
48568
|
+
"Downloaded file"
|
|
48569
|
+
);
|
|
48570
|
+
return {
|
|
48571
|
+
bytes,
|
|
48572
|
+
mimeType: normalizeMimeType(response.headers.get("content-type"))
|
|
48573
|
+
};
|
|
48574
|
+
} catch (err) {
|
|
48575
|
+
if (err instanceof FrappeAPIError || err instanceof ResponseBodyLimitError) {
|
|
48576
|
+
throw err;
|
|
48577
|
+
}
|
|
48578
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
48579
|
+
throw new FrappeAPIError(
|
|
48580
|
+
`Request timed out after ${this.timeoutMs}ms: GET ${path}`,
|
|
48581
|
+
408,
|
|
48582
|
+
null
|
|
48583
|
+
);
|
|
48584
|
+
}
|
|
48585
|
+
throw new FrappeAPIError(
|
|
48586
|
+
`Network error while reading GET ${path}: ${err.message}`,
|
|
48587
|
+
0,
|
|
48588
|
+
null
|
|
48589
|
+
);
|
|
48590
|
+
} finally {
|
|
48591
|
+
clearTimeout(timer);
|
|
48592
|
+
}
|
|
48593
|
+
}
|
|
48303
48594
|
// ── Resource CRUD ───────────────────────────────────────────────────────────
|
|
48304
48595
|
/**
|
|
48305
48596
|
* List documents of a DocType.
|
|
@@ -48421,6 +48712,73 @@ var FrappeClient = class {
|
|
|
48421
48712
|
);
|
|
48422
48713
|
this.invalidate(doctype, name);
|
|
48423
48714
|
}
|
|
48715
|
+
/**
|
|
48716
|
+
* Download one File row through Frappe's authenticated download handler.
|
|
48717
|
+
*
|
|
48718
|
+
* The caller supplies the expected attachment owner as a confused-deputy
|
|
48719
|
+
* guard. The File row is fetched fresh, its local path is validated, and the
|
|
48720
|
+
* response stream is stopped as soon as it exceeds `maxDownloadBytes`.
|
|
48721
|
+
*/
|
|
48722
|
+
async downloadFile(input) {
|
|
48723
|
+
const fileId = input.fileId.trim();
|
|
48724
|
+
const attachedToDoctype = input.attachedToDoctype.trim();
|
|
48725
|
+
const attachedToName = input.attachedToName.trim();
|
|
48726
|
+
if (!fileId) {
|
|
48727
|
+
throw new Error("[FrappeClient] fileId must not be empty");
|
|
48728
|
+
}
|
|
48729
|
+
if (!attachedToDoctype) {
|
|
48730
|
+
throw new Error("[FrappeClient] attachedToDoctype must not be empty");
|
|
48731
|
+
}
|
|
48732
|
+
if (!attachedToName) {
|
|
48733
|
+
throw new Error("[FrappeClient] attachedToName must not be empty");
|
|
48734
|
+
}
|
|
48735
|
+
const metadata = await this.request(
|
|
48736
|
+
"GET",
|
|
48737
|
+
`/api/resource/File/${encodeURIComponent(fileId)}`
|
|
48738
|
+
);
|
|
48739
|
+
const file = metadata.data;
|
|
48740
|
+
if (file.name !== fileId) {
|
|
48741
|
+
throw new Error("[FrappeClient] File response identity mismatch");
|
|
48742
|
+
}
|
|
48743
|
+
if (file.attached_to_doctype !== attachedToDoctype || file.attached_to_name !== attachedToName) {
|
|
48744
|
+
throw new Error(
|
|
48745
|
+
"[FrappeClient] File is not attached to the requested document"
|
|
48746
|
+
);
|
|
48747
|
+
}
|
|
48748
|
+
if (file.is_private !== 0 && file.is_private !== 1) {
|
|
48749
|
+
throw new Error("[FrappeClient] File.is_private must be 0 or 1");
|
|
48750
|
+
}
|
|
48751
|
+
const fileUrl = validateLocalFileUrl(
|
|
48752
|
+
file.file_url,
|
|
48753
|
+
file.is_private === 1
|
|
48754
|
+
);
|
|
48755
|
+
const fileSize = file.file_size;
|
|
48756
|
+
if (fileSize !== void 0 && fileSize !== null) {
|
|
48757
|
+
if (typeof fileSize !== "number" || !Number.isInteger(fileSize) || fileSize < 0) {
|
|
48758
|
+
throw new Error(
|
|
48759
|
+
"[FrappeClient] File.file_size must be a non-negative integer"
|
|
48760
|
+
);
|
|
48761
|
+
}
|
|
48762
|
+
if (fileSize > this.maxDownloadBytes) {
|
|
48763
|
+
throw new ResponseBodyLimitError(
|
|
48764
|
+
"File metadata",
|
|
48765
|
+
fileSize,
|
|
48766
|
+
this.maxDownloadBytes
|
|
48767
|
+
);
|
|
48768
|
+
}
|
|
48769
|
+
}
|
|
48770
|
+
const params = new URLSearchParams({ file_url: fileUrl });
|
|
48771
|
+
const download = await this.requestBinary(
|
|
48772
|
+
`/api/method/frappe.handler.download_file?${params.toString()}`
|
|
48773
|
+
);
|
|
48774
|
+
return {
|
|
48775
|
+
fileId,
|
|
48776
|
+
fileName: sanitizeDownloadFileName(file.file_name, fileId),
|
|
48777
|
+
mimeType: download.mimeType,
|
|
48778
|
+
bytes: download.bytes,
|
|
48779
|
+
isPrivate: file.is_private === 1
|
|
48780
|
+
};
|
|
48781
|
+
}
|
|
48424
48782
|
/**
|
|
48425
48783
|
* Upload file bytes and attach the native File document to another document.
|
|
48426
48784
|
* POST /api/method/upload_file
|
|
@@ -48510,6 +48868,7 @@ function getFrappeClient() {
|
|
|
48510
48868
|
const apiKey = env6("ERPNEXT_API_KEY");
|
|
48511
48869
|
const apiSecret = env6("ERPNEXT_API_SECRET");
|
|
48512
48870
|
const maxUploadBytesRaw = env6("ERPNEXT_MAX_UPLOAD_BYTES");
|
|
48871
|
+
const maxDownloadBytesRaw = env6("ERPNEXT_MAX_DOWNLOAD_BYTES");
|
|
48513
48872
|
if (!url2) {
|
|
48514
48873
|
throw new Error(
|
|
48515
48874
|
"[lib/erpnext] ERPNEXT_URL is required. Set it to your ERPNext instance URL, e.g. http://localhost:8000"
|
|
@@ -48525,7 +48884,8 @@ function getFrappeClient() {
|
|
|
48525
48884
|
apiKey,
|
|
48526
48885
|
apiSecret,
|
|
48527
48886
|
cache: getCache(),
|
|
48528
|
-
maxUploadBytes: maxUploadBytesRaw?.trim() ? Number(maxUploadBytesRaw) : void 0
|
|
48887
|
+
maxUploadBytes: maxUploadBytesRaw?.trim() ? Number(maxUploadBytesRaw) : void 0,
|
|
48888
|
+
maxDownloadBytes: maxDownloadBytesRaw?.trim() ? Number(maxDownloadBytesRaw) : void 0
|
|
48529
48889
|
});
|
|
48530
48890
|
return _client;
|
|
48531
48891
|
}
|
|
@@ -48644,12 +49004,14 @@ var viewer = (name) => ({
|
|
|
48644
49004
|
ui: { resourceUri: `ui://mcp-erpnext/${name}` }
|
|
48645
49005
|
});
|
|
48646
49006
|
var DOCLIST_META = viewer("doclist-viewer");
|
|
49007
|
+
var DOC_META = viewer("doc-viewer");
|
|
48647
49008
|
var INVOICE_META = viewer("invoice-viewer");
|
|
48648
49009
|
var STOCK_META = viewer("stock-viewer");
|
|
48649
49010
|
var CHART_META = viewer("chart-viewer");
|
|
48650
49011
|
var KANBAN_META = viewer("kanban-viewer");
|
|
48651
49012
|
var KPI_META = viewer("kpi-viewer");
|
|
48652
49013
|
var FUNNEL_META = viewer("funnel-viewer");
|
|
49014
|
+
var BUY_EVIDENCE_META = viewer("buy-evidence-viewer");
|
|
48653
49015
|
|
|
48654
49016
|
// src/api/resolve.ts
|
|
48655
49017
|
var NEGATIVE_CACHE_TTL_MS = 15e3;
|
|
@@ -48779,10 +49141,10 @@ function roundedTotalFallbackWarning(original, patched) {
|
|
|
48779
49141
|
|
|
48780
49142
|
// src/tools/sales.ts
|
|
48781
49143
|
function viewerDocument(value, doctype, name) {
|
|
48782
|
-
const
|
|
49144
|
+
const record3 = typeof value === "object" && value !== null ? value : {};
|
|
48783
49145
|
return {
|
|
48784
|
-
...
|
|
48785
|
-
...typeof
|
|
49146
|
+
...record3,
|
|
49147
|
+
...typeof record3.name === "string" || !name ? {} : { name },
|
|
48786
49148
|
doctype
|
|
48787
49149
|
};
|
|
48788
49150
|
}
|
|
@@ -48871,6 +49233,7 @@ var salesTools = [
|
|
|
48871
49233
|
{
|
|
48872
49234
|
name: "erpnext_customer_get",
|
|
48873
49235
|
annotations: { readOnlyHint: true },
|
|
49236
|
+
_meta: DOC_META,
|
|
48874
49237
|
description: "Get a single ERPNext customer by name (ID). Returns all fields including contact details.",
|
|
48875
49238
|
category: "sales",
|
|
48876
49239
|
inputSchema: {
|
|
@@ -48885,7 +49248,7 @@ var salesTools = [
|
|
|
48885
49248
|
throw new Error("[erpnext_customer_get] 'name' is required");
|
|
48886
49249
|
}
|
|
48887
49250
|
const doc = await ctx.client.get("Customer", input.name);
|
|
48888
|
-
return { data: doc };
|
|
49251
|
+
return { data: { ...doc, doctype: "Customer" } };
|
|
48889
49252
|
}
|
|
48890
49253
|
},
|
|
48891
49254
|
{
|
|
@@ -49797,6 +50160,7 @@ var inventoryTools = [
|
|
|
49797
50160
|
{
|
|
49798
50161
|
name: "erpnext_item_get",
|
|
49799
50162
|
annotations: { readOnlyHint: true },
|
|
50163
|
+
_meta: DOC_META,
|
|
49800
50164
|
description: "Get a single ERPNext Item by name/item_code. Returns all fields including pricing and stock details.",
|
|
49801
50165
|
category: "inventory",
|
|
49802
50166
|
inputSchema: {
|
|
@@ -49811,7 +50175,7 @@ var inventoryTools = [
|
|
|
49811
50175
|
throw new Error("[erpnext_item_get] 'name' is required");
|
|
49812
50176
|
}
|
|
49813
50177
|
const doc = await ctx.client.get("Item", input.name);
|
|
49814
|
-
return { data: doc };
|
|
50178
|
+
return { data: { ...doc, doctype: "Item" } };
|
|
49815
50179
|
}
|
|
49816
50180
|
},
|
|
49817
50181
|
{
|
|
@@ -50012,12 +50376,16 @@ var inventoryTools = [
|
|
|
50012
50376
|
name: "erpnext_stock_entry_list",
|
|
50013
50377
|
annotations: { readOnlyHint: true },
|
|
50014
50378
|
_meta: DOCLIST_META,
|
|
50015
|
-
description: "List Stock Entries (material transfers, receipts, issues). Fields: name, stock_entry_type, posting_date, from_warehouse, to_warehouse, total_amount. Filterable by stock_entry_type, date range.",
|
|
50379
|
+
description: "List Stock Entries (material transfers, receipts, issues). Fields: name, stock_entry_type, posting_date, from_warehouse, to_warehouse, total_amount. Filterable by item_code, stock_entry_type, date range.",
|
|
50016
50380
|
category: "inventory",
|
|
50017
50381
|
inputSchema: {
|
|
50018
50382
|
type: "object",
|
|
50019
50383
|
properties: {
|
|
50020
50384
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
50385
|
+
item_code: {
|
|
50386
|
+
type: "string",
|
|
50387
|
+
description: "Exact item code present in a Stock Entry line"
|
|
50388
|
+
},
|
|
50021
50389
|
stock_entry_type: {
|
|
50022
50390
|
type: "string",
|
|
50023
50391
|
description: "Filter by type (Material Issue, Material Receipt, Material Transfer, etc.)"
|
|
@@ -50032,6 +50400,14 @@ var inventoryTools = [
|
|
|
50032
50400
|
handler: async (input, ctx) => {
|
|
50033
50401
|
const limit = input.limit ?? 20;
|
|
50034
50402
|
const filters = [];
|
|
50403
|
+
if (input.item_code) {
|
|
50404
|
+
filters.push([
|
|
50405
|
+
"Stock Entry Detail",
|
|
50406
|
+
"item_code",
|
|
50407
|
+
"=",
|
|
50408
|
+
input.item_code
|
|
50409
|
+
]);
|
|
50410
|
+
}
|
|
50035
50411
|
if (input.stock_entry_type) {
|
|
50036
50412
|
filters.push([
|
|
50037
50413
|
"stock_entry_type",
|
|
@@ -50069,6 +50445,7 @@ var inventoryTools = [
|
|
|
50069
50445
|
{
|
|
50070
50446
|
name: "erpnext_stock_entry_get",
|
|
50071
50447
|
annotations: { readOnlyHint: true },
|
|
50448
|
+
_meta: DOC_META,
|
|
50072
50449
|
description: "Get a single Stock Entry by name. Returns full document with item details.",
|
|
50073
50450
|
category: "inventory",
|
|
50074
50451
|
inputSchema: {
|
|
@@ -50086,7 +50463,7 @@ var inventoryTools = [
|
|
|
50086
50463
|
throw new Error("[erpnext_stock_entry_get] 'name' is required");
|
|
50087
50464
|
}
|
|
50088
50465
|
const doc = await ctx.client.get("Stock Entry", input.name);
|
|
50089
|
-
return { data: doc };
|
|
50466
|
+
return { data: { ...doc, doctype: "Stock Entry" } };
|
|
50090
50467
|
}
|
|
50091
50468
|
},
|
|
50092
50469
|
{
|
|
@@ -50281,6 +50658,7 @@ var accountingTools = [
|
|
|
50281
50658
|
{
|
|
50282
50659
|
name: "erpnext_journal_entry_get",
|
|
50283
50660
|
annotations: { readOnlyHint: true },
|
|
50661
|
+
_meta: DOC_META,
|
|
50284
50662
|
description: "Get a single Journal Entry by name (e.g. JV-00001). Returns full document with accounts.",
|
|
50285
50663
|
category: "accounting",
|
|
50286
50664
|
inputSchema: {
|
|
@@ -50298,7 +50676,7 @@ var accountingTools = [
|
|
|
50298
50676
|
throw new Error("[erpnext_journal_entry_get] 'name' is required");
|
|
50299
50677
|
}
|
|
50300
50678
|
const doc = await ctx.client.get("Journal Entry", input.name);
|
|
50301
|
-
return { data: doc };
|
|
50679
|
+
return { data: { ...doc, doctype: "Journal Entry" } };
|
|
50302
50680
|
}
|
|
50303
50681
|
},
|
|
50304
50682
|
// ── Payment Entries ───────────────────────────────────────────────────────
|
|
@@ -50386,6 +50764,7 @@ var accountingTools = [
|
|
|
50386
50764
|
{
|
|
50387
50765
|
name: "erpnext_payment_entry_get",
|
|
50388
50766
|
annotations: { readOnlyHint: true },
|
|
50767
|
+
_meta: DOC_META,
|
|
50389
50768
|
description: "Get a single Payment Entry by name (e.g. PE-00001). Returns full document including references.",
|
|
50390
50769
|
category: "accounting",
|
|
50391
50770
|
inputSchema: {
|
|
@@ -50403,7 +50782,7 @@ var accountingTools = [
|
|
|
50403
50782
|
throw new Error("[erpnext_payment_entry_get] 'name' is required");
|
|
50404
50783
|
}
|
|
50405
50784
|
const doc = await ctx.client.get("Payment Entry", input.name);
|
|
50406
|
-
return { data: doc };
|
|
50785
|
+
return { data: { ...doc, doctype: "Payment Entry" } };
|
|
50407
50786
|
}
|
|
50408
50787
|
},
|
|
50409
50788
|
{
|
|
@@ -50529,6 +50908,7 @@ var hrTools = [
|
|
|
50529
50908
|
{
|
|
50530
50909
|
name: "erpnext_employee_get",
|
|
50531
50910
|
annotations: { readOnlyHint: true },
|
|
50911
|
+
_meta: DOC_META,
|
|
50532
50912
|
description: "Get a single Employee by name/ID (e.g. HR-EMP-00001). Returns all fields.",
|
|
50533
50913
|
category: "hr",
|
|
50534
50914
|
inputSchema: {
|
|
@@ -50553,7 +50933,7 @@ var hrTools = [
|
|
|
50553
50933
|
{ allowPartialMatch: false, inputPath: "name" }
|
|
50554
50934
|
);
|
|
50555
50935
|
const doc = await ctx.client.get("Employee", employeeId);
|
|
50556
|
-
return { data: doc };
|
|
50936
|
+
return { data: { ...doc, doctype: "Employee" } };
|
|
50557
50937
|
}
|
|
50558
50938
|
},
|
|
50559
50939
|
// ── Attendance ────────────────────────────────────────────────────────────
|
|
@@ -50704,6 +51084,7 @@ var hrTools = [
|
|
|
50704
51084
|
{
|
|
50705
51085
|
name: "erpnext_leave_application_get",
|
|
50706
51086
|
annotations: { readOnlyHint: true },
|
|
51087
|
+
_meta: DOC_META,
|
|
50707
51088
|
description: "Get a single Leave Application by name. Returns full document.",
|
|
50708
51089
|
category: "hr",
|
|
50709
51090
|
inputSchema: {
|
|
@@ -50721,7 +51102,7 @@ var hrTools = [
|
|
|
50721
51102
|
"Leave Application",
|
|
50722
51103
|
input.name
|
|
50723
51104
|
);
|
|
50724
|
-
return { data: doc };
|
|
51105
|
+
return { data: { ...doc, doctype: "Leave Application" } };
|
|
50725
51106
|
}
|
|
50726
51107
|
},
|
|
50727
51108
|
{
|
|
@@ -50867,6 +51248,7 @@ var hrTools = [
|
|
|
50867
51248
|
{
|
|
50868
51249
|
name: "erpnext_salary_slip_get",
|
|
50869
51250
|
annotations: { readOnlyHint: true },
|
|
51251
|
+
_meta: DOC_META,
|
|
50870
51252
|
description: "Get a single Salary Slip by name/ID. Returns all fields including earnings and deductions.",
|
|
50871
51253
|
category: "hr",
|
|
50872
51254
|
inputSchema: {
|
|
@@ -50884,7 +51266,7 @@ var hrTools = [
|
|
|
50884
51266
|
throw new Error("[erpnext_salary_slip_get] 'name' is required");
|
|
50885
51267
|
}
|
|
50886
51268
|
const doc = await ctx.client.get("Salary Slip", input.name);
|
|
50887
|
-
return { data: doc };
|
|
51269
|
+
return { data: { ...doc, doctype: "Salary Slip" } };
|
|
50888
51270
|
}
|
|
50889
51271
|
},
|
|
50890
51272
|
// ── Payroll Entries ───────────────────────────────────────────────────────
|
|
@@ -51274,8 +51656,8 @@ async function applyAssignment(doctype, name, assignment, ctx, failureContext) {
|
|
|
51274
51656
|
throw new Error(`${failureContext}: ${reason}`, { cause: error2 });
|
|
51275
51657
|
}
|
|
51276
51658
|
const todos = Array.isArray(nativeResult) ? nativeResult.map((todo) => {
|
|
51277
|
-
const
|
|
51278
|
-
return { owner:
|
|
51659
|
+
const record3 = todo;
|
|
51660
|
+
return { owner: record3.owner, name: record3.name };
|
|
51279
51661
|
}) : [];
|
|
51280
51662
|
return { notify_user: true, assignees: assignment.assignees, todos };
|
|
51281
51663
|
}
|
|
@@ -51292,8 +51674,8 @@ async function removeAssignment(doctype, name, assignee, ctx, failureContext) {
|
|
|
51292
51674
|
throw new Error(`${failureContext}: ${reason}`, { cause: error2 });
|
|
51293
51675
|
}
|
|
51294
51676
|
const todos = Array.isArray(nativeResult) ? nativeResult.map((todo) => {
|
|
51295
|
-
const
|
|
51296
|
-
return { owner:
|
|
51677
|
+
const record3 = todo;
|
|
51678
|
+
return { owner: record3.owner, name: record3.name };
|
|
51297
51679
|
}) : [];
|
|
51298
51680
|
return { removed: assignee, remaining: todos };
|
|
51299
51681
|
}
|
|
@@ -51375,6 +51757,7 @@ var projectTools = [
|
|
|
51375
51757
|
{
|
|
51376
51758
|
name: "erpnext_project_get",
|
|
51377
51759
|
annotations: { readOnlyHint: true },
|
|
51760
|
+
_meta: DOC_META,
|
|
51378
51761
|
description: "Get a single Project by name. Returns full document including tasks summary.",
|
|
51379
51762
|
category: "project",
|
|
51380
51763
|
inputSchema: {
|
|
@@ -51389,7 +51772,7 @@ var projectTools = [
|
|
|
51389
51772
|
throw new Error("[erpnext_project_get] 'name' is required");
|
|
51390
51773
|
}
|
|
51391
51774
|
const doc = await ctx.client.get("Project", input.name);
|
|
51392
|
-
return { data: doc };
|
|
51775
|
+
return { data: { ...doc, doctype: "Project" } };
|
|
51393
51776
|
}
|
|
51394
51777
|
},
|
|
51395
51778
|
// ── Tasks ─────────────────────────────────────────────────────────────────
|
|
@@ -51554,6 +51937,7 @@ var projectTools = [
|
|
|
51554
51937
|
{
|
|
51555
51938
|
name: "erpnext_task_get",
|
|
51556
51939
|
annotations: { readOnlyHint: true },
|
|
51940
|
+
_meta: DOC_META,
|
|
51557
51941
|
description: "Get a single Task by name. Returns full document including description and dependencies.",
|
|
51558
51942
|
category: "project",
|
|
51559
51943
|
inputSchema: {
|
|
@@ -51568,7 +51952,7 @@ var projectTools = [
|
|
|
51568
51952
|
throw new Error("[erpnext_task_get] 'name' is required");
|
|
51569
51953
|
}
|
|
51570
51954
|
const doc = await ctx.client.get("Task", input.name);
|
|
51571
|
-
return { data: doc };
|
|
51955
|
+
return { data: { ...doc, doctype: "Task" } };
|
|
51572
51956
|
}
|
|
51573
51957
|
},
|
|
51574
51958
|
{
|
|
@@ -51744,6 +52128,7 @@ var projectTools = [
|
|
|
51744
52128
|
{
|
|
51745
52129
|
name: "erpnext_timesheet_get",
|
|
51746
52130
|
annotations: { readOnlyHint: true },
|
|
52131
|
+
_meta: DOC_META,
|
|
51747
52132
|
description: "Get a single Timesheet by name. Returns full document with time log details.",
|
|
51748
52133
|
category: "project",
|
|
51749
52134
|
inputSchema: {
|
|
@@ -51758,7 +52143,7 @@ var projectTools = [
|
|
|
51758
52143
|
throw new Error("[erpnext_timesheet_get] 'name' is required");
|
|
51759
52144
|
}
|
|
51760
52145
|
const doc = await ctx.client.get("Timesheet", input.name);
|
|
51761
|
-
return { data: doc };
|
|
52146
|
+
return { data: { ...doc, doctype: "Timesheet" } };
|
|
51762
52147
|
}
|
|
51763
52148
|
},
|
|
51764
52149
|
{
|
|
@@ -51880,6 +52265,7 @@ var purchasingTools = [
|
|
|
51880
52265
|
{
|
|
51881
52266
|
name: "erpnext_supplier_get",
|
|
51882
52267
|
annotations: { readOnlyHint: true },
|
|
52268
|
+
_meta: DOC_META,
|
|
51883
52269
|
description: "Get a single ERPNext supplier by name (ID). Returns all fields including contact details.",
|
|
51884
52270
|
category: "purchasing",
|
|
51885
52271
|
inputSchema: {
|
|
@@ -51894,7 +52280,7 @@ var purchasingTools = [
|
|
|
51894
52280
|
throw new Error("[erpnext_supplier_get] 'name' is required");
|
|
51895
52281
|
}
|
|
51896
52282
|
const doc = await ctx.client.get("Supplier", input.name);
|
|
51897
|
-
return { data: doc };
|
|
52283
|
+
return { data: { ...doc, doctype: "Supplier" } };
|
|
51898
52284
|
}
|
|
51899
52285
|
},
|
|
51900
52286
|
{
|
|
@@ -52023,6 +52409,7 @@ var purchasingTools = [
|
|
|
52023
52409
|
{
|
|
52024
52410
|
name: "erpnext_purchase_order_get",
|
|
52025
52411
|
annotations: { readOnlyHint: true },
|
|
52412
|
+
_meta: DOC_META,
|
|
52026
52413
|
description: "Get a single Purchase Order by name (e.g. PO-00001). Returns full document with line items.",
|
|
52027
52414
|
category: "purchasing",
|
|
52028
52415
|
inputSchema: {
|
|
@@ -52040,7 +52427,7 @@ var purchasingTools = [
|
|
|
52040
52427
|
throw new Error("[erpnext_purchase_order_get] 'name' is required");
|
|
52041
52428
|
}
|
|
52042
52429
|
const doc = await ctx.client.get("Purchase Order", input.name);
|
|
52043
|
-
return { data: doc };
|
|
52430
|
+
return { data: { ...doc, doctype: "Purchase Order" } };
|
|
52044
52431
|
}
|
|
52045
52432
|
},
|
|
52046
52433
|
{
|
|
@@ -52191,6 +52578,7 @@ var purchasingTools = [
|
|
|
52191
52578
|
{
|
|
52192
52579
|
name: "erpnext_purchase_invoice_get",
|
|
52193
52580
|
annotations: { readOnlyHint: true },
|
|
52581
|
+
_meta: DOC_META,
|
|
52194
52582
|
description: "Get a single Purchase Invoice by name (e.g. PINV-00001). Returns full document with line items.",
|
|
52195
52583
|
category: "purchasing",
|
|
52196
52584
|
inputSchema: {
|
|
@@ -52211,7 +52599,7 @@ var purchasingTools = [
|
|
|
52211
52599
|
"Purchase Invoice",
|
|
52212
52600
|
input.name
|
|
52213
52601
|
);
|
|
52214
|
-
return { data: doc };
|
|
52602
|
+
return { data: { ...doc, doctype: "Purchase Invoice" } };
|
|
52215
52603
|
}
|
|
52216
52604
|
},
|
|
52217
52605
|
// ── Purchase Receipts ─────────────────────────────────────────────────────
|
|
@@ -52285,6 +52673,7 @@ var purchasingTools = [
|
|
|
52285
52673
|
{
|
|
52286
52674
|
name: "erpnext_purchase_receipt_get",
|
|
52287
52675
|
annotations: { readOnlyHint: true },
|
|
52676
|
+
_meta: DOC_META,
|
|
52288
52677
|
description: "Get a single Purchase Receipt by name (e.g. MAT-PRE-00001). Returns full document with received items.",
|
|
52289
52678
|
category: "purchasing",
|
|
52290
52679
|
inputSchema: {
|
|
@@ -52305,7 +52694,7 @@ var purchasingTools = [
|
|
|
52305
52694
|
"Purchase Receipt",
|
|
52306
52695
|
input.name
|
|
52307
52696
|
);
|
|
52308
|
-
return { data: doc };
|
|
52697
|
+
return { data: { ...doc, doctype: "Purchase Receipt" } };
|
|
52309
52698
|
}
|
|
52310
52699
|
},
|
|
52311
52700
|
// ── Supplier Quotations ───────────────────────────────────────────────────
|
|
@@ -52450,6 +52839,7 @@ var deliveryTools = [
|
|
|
52450
52839
|
{
|
|
52451
52840
|
name: "erpnext_delivery_note_get",
|
|
52452
52841
|
annotations: { readOnlyHint: true },
|
|
52842
|
+
_meta: DOC_META,
|
|
52453
52843
|
description: "Get a single Delivery Note by name (e.g. MAT-DN-00001). Returns full document with delivered items.",
|
|
52454
52844
|
category: "delivery",
|
|
52455
52845
|
inputSchema: {
|
|
@@ -52467,7 +52857,7 @@ var deliveryTools = [
|
|
|
52467
52857
|
throw new Error("[erpnext_delivery_note_get] 'name' is required");
|
|
52468
52858
|
}
|
|
52469
52859
|
const doc = await ctx.client.get("Delivery Note", input.name);
|
|
52470
|
-
return { data: doc };
|
|
52860
|
+
return { data: { ...doc, doctype: "Delivery Note" } };
|
|
52471
52861
|
}
|
|
52472
52862
|
},
|
|
52473
52863
|
{
|
|
@@ -52600,6 +52990,7 @@ var deliveryTools = [
|
|
|
52600
52990
|
{
|
|
52601
52991
|
name: "erpnext_shipment_get",
|
|
52602
52992
|
annotations: { readOnlyHint: true },
|
|
52993
|
+
_meta: DOC_META,
|
|
52603
52994
|
description: "Get a single Shipment by name. Returns full shipment details including parcels.",
|
|
52604
52995
|
category: "delivery",
|
|
52605
52996
|
inputSchema: {
|
|
@@ -52614,7 +53005,7 @@ var deliveryTools = [
|
|
|
52614
53005
|
throw new Error("[erpnext_shipment_get] 'name' is required");
|
|
52615
53006
|
}
|
|
52616
53007
|
const doc = await ctx.client.get("Shipment", input.name);
|
|
52617
|
-
return { data: doc };
|
|
53008
|
+
return { data: { ...doc, doctype: "Shipment" } };
|
|
52618
53009
|
}
|
|
52619
53010
|
}
|
|
52620
53011
|
];
|
|
@@ -52694,6 +53085,7 @@ var manufacturingTools = [
|
|
|
52694
53085
|
{
|
|
52695
53086
|
name: "erpnext_bom_get",
|
|
52696
53087
|
annotations: { readOnlyHint: true },
|
|
53088
|
+
_meta: DOC_META,
|
|
52697
53089
|
description: "Get a single BOM by name (e.g. BOM-ITEM-00001). Returns full document with raw materials and operations.",
|
|
52698
53090
|
category: "manufacturing",
|
|
52699
53091
|
inputSchema: {
|
|
@@ -52708,7 +53100,7 @@ var manufacturingTools = [
|
|
|
52708
53100
|
throw new Error("[erpnext_bom_get] 'name' is required");
|
|
52709
53101
|
}
|
|
52710
53102
|
const doc = await ctx.client.get("BOM", input.name);
|
|
52711
|
-
return { data: doc };
|
|
53103
|
+
return { data: { ...doc, doctype: "BOM" } };
|
|
52712
53104
|
}
|
|
52713
53105
|
},
|
|
52714
53106
|
// ── Work Orders ───────────────────────────────────────────────────────────
|
|
@@ -52786,6 +53178,7 @@ var manufacturingTools = [
|
|
|
52786
53178
|
{
|
|
52787
53179
|
name: "erpnext_work_order_get",
|
|
52788
53180
|
annotations: { readOnlyHint: true },
|
|
53181
|
+
_meta: DOC_META,
|
|
52789
53182
|
description: "Get a single Work Order by name (e.g. MFG-WO-00001). Returns full document with operations and materials.",
|
|
52790
53183
|
category: "manufacturing",
|
|
52791
53184
|
inputSchema: {
|
|
@@ -52803,7 +53196,7 @@ var manufacturingTools = [
|
|
|
52803
53196
|
throw new Error("[erpnext_work_order_get] 'name' is required");
|
|
52804
53197
|
}
|
|
52805
53198
|
const doc = await ctx.client.get("Work Order", input.name);
|
|
52806
|
-
return { data: doc };
|
|
53199
|
+
return { data: { ...doc, doctype: "Work Order" } };
|
|
52807
53200
|
}
|
|
52808
53201
|
},
|
|
52809
53202
|
{
|
|
@@ -52919,6 +53312,7 @@ var manufacturingTools = [
|
|
|
52919
53312
|
{
|
|
52920
53313
|
name: "erpnext_job_card_get",
|
|
52921
53314
|
annotations: { readOnlyHint: true },
|
|
53315
|
+
_meta: DOC_META,
|
|
52922
53316
|
description: "Get a single Job Card by name. Returns full document with time logs and material transfers.",
|
|
52923
53317
|
category: "manufacturing",
|
|
52924
53318
|
inputSchema: {
|
|
@@ -52933,7 +53327,7 @@ var manufacturingTools = [
|
|
|
52933
53327
|
throw new Error("[erpnext_job_card_get] 'name' is required");
|
|
52934
53328
|
}
|
|
52935
53329
|
const doc = await ctx.client.get("Job Card", input.name);
|
|
52936
|
-
return { data: doc };
|
|
53330
|
+
return { data: { ...doc, doctype: "Job Card" } };
|
|
52937
53331
|
}
|
|
52938
53332
|
}
|
|
52939
53333
|
];
|
|
@@ -52995,6 +53389,7 @@ var crmTools = [
|
|
|
52995
53389
|
{
|
|
52996
53390
|
name: "erpnext_lead_get",
|
|
52997
53391
|
annotations: { readOnlyHint: true },
|
|
53392
|
+
_meta: DOC_META,
|
|
52998
53393
|
description: "Get a single CRM Lead by name. Returns all lead details including contact info.",
|
|
52999
53394
|
category: "crm",
|
|
53000
53395
|
inputSchema: {
|
|
@@ -53009,7 +53404,7 @@ var crmTools = [
|
|
|
53009
53404
|
throw new Error("[erpnext_lead_get] 'name' is required");
|
|
53010
53405
|
}
|
|
53011
53406
|
const doc = await ctx.client.get("Lead", input.name);
|
|
53012
|
-
return { data: doc };
|
|
53407
|
+
return { data: { ...doc, doctype: "Lead" } };
|
|
53013
53408
|
}
|
|
53014
53409
|
},
|
|
53015
53410
|
{
|
|
@@ -53141,6 +53536,7 @@ var crmTools = [
|
|
|
53141
53536
|
{
|
|
53142
53537
|
name: "erpnext_opportunity_get",
|
|
53143
53538
|
annotations: { readOnlyHint: true },
|
|
53539
|
+
_meta: DOC_META,
|
|
53144
53540
|
description: "Get a single CRM Opportunity by name. Returns full details including items and competitors.",
|
|
53145
53541
|
category: "crm",
|
|
53146
53542
|
inputSchema: {
|
|
@@ -53155,7 +53551,7 @@ var crmTools = [
|
|
|
53155
53551
|
throw new Error("[erpnext_opportunity_get] 'name' is required");
|
|
53156
53552
|
}
|
|
53157
53553
|
const doc = await ctx.client.get("Opportunity", input.name);
|
|
53158
|
-
return { data: doc };
|
|
53554
|
+
return { data: { ...doc, doctype: "Opportunity" } };
|
|
53159
53555
|
}
|
|
53160
53556
|
},
|
|
53161
53557
|
// ── Contacts ──────────────────────────────────────────────────────────────
|
|
@@ -53210,6 +53606,7 @@ var crmTools = [
|
|
|
53210
53606
|
{
|
|
53211
53607
|
name: "erpnext_contact_get",
|
|
53212
53608
|
annotations: { readOnlyHint: true },
|
|
53609
|
+
_meta: DOC_META,
|
|
53213
53610
|
description: "Get a single Contact by name. Returns all contact details.",
|
|
53214
53611
|
category: "crm",
|
|
53215
53612
|
inputSchema: {
|
|
@@ -53224,7 +53621,7 @@ var crmTools = [
|
|
|
53224
53621
|
throw new Error("[erpnext_contact_get] 'name' is required");
|
|
53225
53622
|
}
|
|
53226
53623
|
const doc = await ctx.client.get("Contact", input.name);
|
|
53227
|
-
return { data: doc };
|
|
53624
|
+
return { data: { ...doc, doctype: "Contact" } };
|
|
53228
53625
|
}
|
|
53229
53626
|
},
|
|
53230
53627
|
// ── Campaigns ─────────────────────────────────────────────────────────────
|
|
@@ -53374,6 +53771,7 @@ var assetsTools = [
|
|
|
53374
53771
|
{
|
|
53375
53772
|
name: "erpnext_asset_get",
|
|
53376
53773
|
annotations: { readOnlyHint: true },
|
|
53774
|
+
_meta: DOC_META,
|
|
53377
53775
|
description: "Get a single Asset by name. Returns full details including depreciation schedule and maintenance logs.",
|
|
53378
53776
|
category: "assets",
|
|
53379
53777
|
inputSchema: {
|
|
@@ -53388,7 +53786,7 @@ var assetsTools = [
|
|
|
53388
53786
|
throw new Error("[erpnext_asset_get] 'name' is required");
|
|
53389
53787
|
}
|
|
53390
53788
|
const doc = await ctx.client.get("Asset", input.name);
|
|
53391
|
-
return { data: doc };
|
|
53789
|
+
return { data: { ...doc, doctype: "Asset" } };
|
|
53392
53790
|
}
|
|
53393
53791
|
},
|
|
53394
53792
|
{
|
|
@@ -53524,6 +53922,7 @@ var assetsTools = [
|
|
|
53524
53922
|
{
|
|
53525
53923
|
name: "erpnext_asset_movement_get",
|
|
53526
53924
|
annotations: { readOnlyHint: true },
|
|
53925
|
+
_meta: DOC_META,
|
|
53527
53926
|
description: "Get a single Asset Movement by name. Returns full details including assets moved.",
|
|
53528
53927
|
category: "assets",
|
|
53529
53928
|
inputSchema: {
|
|
@@ -53538,7 +53937,7 @@ var assetsTools = [
|
|
|
53538
53937
|
throw new Error("[erpnext_asset_movement_get] 'name' is required");
|
|
53539
53938
|
}
|
|
53540
53939
|
const doc = await ctx.client.get("Asset Movement", input.name);
|
|
53541
|
-
return { data: doc };
|
|
53940
|
+
return { data: { ...doc, doctype: "Asset Movement" } };
|
|
53542
53941
|
}
|
|
53543
53942
|
},
|
|
53544
53943
|
// ── Asset Maintenance ─────────────────────────────────────────────────────
|
|
@@ -53595,6 +53994,7 @@ var assetsTools = [
|
|
|
53595
53994
|
{
|
|
53596
53995
|
name: "erpnext_asset_maintenance_get",
|
|
53597
53996
|
annotations: { readOnlyHint: true },
|
|
53997
|
+
_meta: DOC_META,
|
|
53598
53998
|
description: "Get a single Asset Maintenance record by name. Returns full details including maintenance tasks.",
|
|
53599
53999
|
category: "assets",
|
|
53600
54000
|
inputSchema: {
|
|
@@ -53612,7 +54012,7 @@ var assetsTools = [
|
|
|
53612
54012
|
"Asset Maintenance",
|
|
53613
54013
|
input.name
|
|
53614
54014
|
);
|
|
53615
|
-
return { data: doc };
|
|
54015
|
+
return { data: { ...doc, doctype: "Asset Maintenance" } };
|
|
53616
54016
|
}
|
|
53617
54017
|
},
|
|
53618
54018
|
// ── Asset Categories ──────────────────────────────────────────────────────
|
|
@@ -53729,11 +54129,166 @@ function isEmptyPartyAccountCurrencyNone(text) {
|
|
|
53729
54129
|
}
|
|
53730
54130
|
|
|
53731
54131
|
// src/tools/operations.ts
|
|
54132
|
+
var DOC_LIST_MAX_LIMIT = 5e3;
|
|
54133
|
+
var DOC_LIST_MAX_OFFSET = 1e5;
|
|
54134
|
+
function bytesToBase64(bytes) {
|
|
54135
|
+
const chunkSize = 32 * 1024;
|
|
54136
|
+
let binary = "";
|
|
54137
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
54138
|
+
binary += String.fromCharCode(
|
|
54139
|
+
...bytes.subarray(offset, Math.min(offset + chunkSize, bytes.byteLength))
|
|
54140
|
+
);
|
|
54141
|
+
}
|
|
54142
|
+
return btoa(binary);
|
|
54143
|
+
}
|
|
53732
54144
|
async function withPiAccountHintsIfNeeded(doctype, fn) {
|
|
53733
54145
|
return isPurchaseInvoiceDoctype(doctype) ? await withPurchaseInvoiceAccountHints(fn) : await fn();
|
|
53734
54146
|
}
|
|
53735
54147
|
var operationsTools = [
|
|
53736
54148
|
// ── File Attachments ───────────────────────────────────────────────────────
|
|
54149
|
+
{
|
|
54150
|
+
name: "erpnext_file_list",
|
|
54151
|
+
annotations: { readOnlyHint: true },
|
|
54152
|
+
description: "List the files attached to an ERPNext document. Returns name, size, privacy and URL for each attachment. Pairs with erpnext_file_upload: that one attaches, this one reads back.",
|
|
54153
|
+
category: "operations",
|
|
54154
|
+
inputSchema: {
|
|
54155
|
+
type: "object",
|
|
54156
|
+
properties: {
|
|
54157
|
+
attached_to_doctype: {
|
|
54158
|
+
type: "string",
|
|
54159
|
+
description: "DocType of the document whose attachments to list.",
|
|
54160
|
+
minLength: 1
|
|
54161
|
+
},
|
|
54162
|
+
attached_to_name: {
|
|
54163
|
+
type: "string",
|
|
54164
|
+
description: "Name/ID of the document whose attachments to list.",
|
|
54165
|
+
minLength: 1
|
|
54166
|
+
},
|
|
54167
|
+
limit: {
|
|
54168
|
+
type: "number",
|
|
54169
|
+
description: "Maximum number of files to return. Defaults to 50.",
|
|
54170
|
+
minimum: 1,
|
|
54171
|
+
maximum: 500
|
|
54172
|
+
}
|
|
54173
|
+
},
|
|
54174
|
+
required: ["attached_to_doctype", "attached_to_name"]
|
|
54175
|
+
},
|
|
54176
|
+
handler: async (input, ctx) => {
|
|
54177
|
+
for (const field of ["attached_to_doctype", "attached_to_name"]) {
|
|
54178
|
+
if (typeof input[field] !== "string" || !input[field].trim()) {
|
|
54179
|
+
throw new Error(
|
|
54180
|
+
`[erpnext_file_list] '${field}' must be a non-empty string`
|
|
54181
|
+
);
|
|
54182
|
+
}
|
|
54183
|
+
}
|
|
54184
|
+
if (input.limit !== void 0 && (typeof input.limit !== "number" || !Number.isInteger(input.limit) || input.limit < 1 || input.limit > 500)) {
|
|
54185
|
+
throw new Error(
|
|
54186
|
+
"[erpnext_file_list] 'limit' must be an integer between 1 and 500"
|
|
54187
|
+
);
|
|
54188
|
+
}
|
|
54189
|
+
const files = await ctx.client.list("File", {
|
|
54190
|
+
fields: [
|
|
54191
|
+
"name",
|
|
54192
|
+
"file_name",
|
|
54193
|
+
"file_url",
|
|
54194
|
+
"file_size",
|
|
54195
|
+
"is_private",
|
|
54196
|
+
"attached_to_field",
|
|
54197
|
+
"creation",
|
|
54198
|
+
"modified",
|
|
54199
|
+
"owner"
|
|
54200
|
+
],
|
|
54201
|
+
filters: [
|
|
54202
|
+
["attached_to_doctype", "=", input.attached_to_doctype],
|
|
54203
|
+
["attached_to_name", "=", input.attached_to_name]
|
|
54204
|
+
],
|
|
54205
|
+
order_by: "creation desc",
|
|
54206
|
+
limit: input.limit ?? 50
|
|
54207
|
+
});
|
|
54208
|
+
return {
|
|
54209
|
+
count: files.length,
|
|
54210
|
+
data: files.map((file) => ({
|
|
54211
|
+
name: file.name,
|
|
54212
|
+
file_name: file.file_name,
|
|
54213
|
+
file_url: file.file_url,
|
|
54214
|
+
file_size: file.file_size ?? null,
|
|
54215
|
+
is_private: file.is_private === 1,
|
|
54216
|
+
attached_to_field: file.attached_to_field ?? null,
|
|
54217
|
+
creation: file.creation,
|
|
54218
|
+
modified: file.modified,
|
|
54219
|
+
owner: file.owner
|
|
54220
|
+
}))
|
|
54221
|
+
};
|
|
54222
|
+
}
|
|
54223
|
+
},
|
|
54224
|
+
{
|
|
54225
|
+
name: "erpnext_file_download",
|
|
54226
|
+
annotations: { readOnlyHint: true },
|
|
54227
|
+
_meta: {
|
|
54228
|
+
ui: {
|
|
54229
|
+
resourceUri: DOC_META.ui.resourceUri,
|
|
54230
|
+
visibility: ["app"]
|
|
54231
|
+
}
|
|
54232
|
+
},
|
|
54233
|
+
description: "Download one ERPNext attachment for the document viewer. The tool accepts a File ID, verifies its document attachment, and returns one embedded binary resource.",
|
|
54234
|
+
category: "operations",
|
|
54235
|
+
inputSchema: {
|
|
54236
|
+
type: "object",
|
|
54237
|
+
properties: {
|
|
54238
|
+
file_id: {
|
|
54239
|
+
type: "string",
|
|
54240
|
+
description: "Native ERPNext File.name identifier, never a URL.",
|
|
54241
|
+
minLength: 1
|
|
54242
|
+
},
|
|
54243
|
+
attached_to_doctype: {
|
|
54244
|
+
type: "string",
|
|
54245
|
+
description: "Expected parent document DocType.",
|
|
54246
|
+
minLength: 1
|
|
54247
|
+
},
|
|
54248
|
+
attached_to_name: {
|
|
54249
|
+
type: "string",
|
|
54250
|
+
description: "Expected parent document name/ID.",
|
|
54251
|
+
minLength: 1
|
|
54252
|
+
}
|
|
54253
|
+
},
|
|
54254
|
+
required: ["file_id", "attached_to_doctype", "attached_to_name"],
|
|
54255
|
+
additionalProperties: false
|
|
54256
|
+
},
|
|
54257
|
+
handler: async (input, ctx) => {
|
|
54258
|
+
for (const field of [
|
|
54259
|
+
"file_id",
|
|
54260
|
+
"attached_to_doctype",
|
|
54261
|
+
"attached_to_name"
|
|
54262
|
+
]) {
|
|
54263
|
+
if (typeof input[field] !== "string" || !input[field].trim()) {
|
|
54264
|
+
throw new Error(
|
|
54265
|
+
`[erpnext_file_download] '${field}' must be a non-empty string`
|
|
54266
|
+
);
|
|
54267
|
+
}
|
|
54268
|
+
}
|
|
54269
|
+
const file = await ctx.client.downloadFile({
|
|
54270
|
+
fileId: input.file_id.trim(),
|
|
54271
|
+
attachedToDoctype: input.attached_to_doctype.trim(),
|
|
54272
|
+
attachedToName: input.attached_to_name.trim()
|
|
54273
|
+
});
|
|
54274
|
+
return {
|
|
54275
|
+
content: [
|
|
54276
|
+
{
|
|
54277
|
+
type: "text",
|
|
54278
|
+
text: `Prepared ${file.fileName} for download (${file.bytes.byteLength} bytes).`
|
|
54279
|
+
},
|
|
54280
|
+
{
|
|
54281
|
+
type: "resource",
|
|
54282
|
+
resource: {
|
|
54283
|
+
uri: `file:///${encodeURIComponent(file.fileName)}`,
|
|
54284
|
+
mimeType: file.mimeType,
|
|
54285
|
+
blob: bytesToBase64(file.bytes)
|
|
54286
|
+
}
|
|
54287
|
+
}
|
|
54288
|
+
]
|
|
54289
|
+
};
|
|
54290
|
+
}
|
|
54291
|
+
},
|
|
53737
54292
|
{
|
|
53738
54293
|
name: "erpnext_file_upload",
|
|
53739
54294
|
annotations: { destructiveHint: true },
|
|
@@ -54047,6 +54602,7 @@ var operationsTools = [
|
|
|
54047
54602
|
{
|
|
54048
54603
|
name: "erpnext_doc_get",
|
|
54049
54604
|
annotations: { readOnlyHint: true },
|
|
54605
|
+
_meta: DOC_META,
|
|
54050
54606
|
description: "Get any ERPNext document by DocType and name. Useful for DocTypes not covered by dedicated tools. Returns the full document with all fields.",
|
|
54051
54607
|
category: "operations",
|
|
54052
54608
|
inputSchema: {
|
|
@@ -54064,17 +54620,18 @@ var operationsTools = [
|
|
|
54064
54620
|
required: ["doctype", "name"]
|
|
54065
54621
|
},
|
|
54066
54622
|
handler: async (input, ctx) => {
|
|
54067
|
-
if (!input.doctype) {
|
|
54623
|
+
if (typeof input.doctype !== "string" || !input.doctype.trim()) {
|
|
54068
54624
|
throw new Error("[erpnext_doc_get] 'doctype' is required");
|
|
54069
54625
|
}
|
|
54070
|
-
if (!input.name) {
|
|
54626
|
+
if (typeof input.name !== "string" || !input.name.trim()) {
|
|
54071
54627
|
throw new Error("[erpnext_doc_get] 'name' is required");
|
|
54072
54628
|
}
|
|
54629
|
+
const doctype = input.doctype.trim();
|
|
54073
54630
|
const doc = await ctx.client.get(
|
|
54074
|
-
|
|
54075
|
-
input.name
|
|
54631
|
+
doctype,
|
|
54632
|
+
input.name.trim()
|
|
54076
54633
|
);
|
|
54077
|
-
return { data: doc };
|
|
54634
|
+
return { data: { ...doc, doctype } };
|
|
54078
54635
|
}
|
|
54079
54636
|
},
|
|
54080
54637
|
// ── Generic List ──────────────────────────────────────────────────────────
|
|
@@ -54082,7 +54639,7 @@ var operationsTools = [
|
|
|
54082
54639
|
name: "erpnext_doc_list",
|
|
54083
54640
|
annotations: { readOnlyHint: true },
|
|
54084
54641
|
_meta: DOCLIST_META,
|
|
54085
|
-
description: "List any ERPNext documents by DocType. Useful for DocTypes not covered by dedicated tools. Supports field selection, filters (as JSON array), and
|
|
54642
|
+
description: "List any ERPNext documents by DocType. Useful for DocTypes not covered by dedicated tools. Supports field selection, filters (as JSON array), bounded pagination, and an optional fresh read.",
|
|
54086
54643
|
category: "operations",
|
|
54087
54644
|
inputSchema: {
|
|
54088
54645
|
type: "object",
|
|
@@ -54098,13 +54655,70 @@ var operationsTools = [
|
|
|
54098
54655
|
},
|
|
54099
54656
|
filters: {
|
|
54100
54657
|
type: "array",
|
|
54101
|
-
description: 'Frappe filters as array of [fieldname, operator, value] tuples. Example: [["status","=","Open"],["company","=","Acme"]]',
|
|
54658
|
+
description: 'Frappe filters as array of [fieldname, operator, value] tuples, or [child doctype, fieldname, operator, value] to filter on a child table. Values may be strings, numbers, booleans, null, or string/number arrays for in/not in. Example: [["status","=","Open"],["company","=","Acme"]]',
|
|
54102
54659
|
items: {
|
|
54103
54660
|
type: "array",
|
|
54104
|
-
|
|
54661
|
+
anyOf: [
|
|
54662
|
+
{
|
|
54663
|
+
prefixItems: [
|
|
54664
|
+
{ type: "string", minLength: 1 },
|
|
54665
|
+
{ type: "string", minLength: 1 },
|
|
54666
|
+
{
|
|
54667
|
+
oneOf: [
|
|
54668
|
+
{ type: ["string", "number", "boolean", "null"] },
|
|
54669
|
+
{
|
|
54670
|
+
type: "array",
|
|
54671
|
+
items: { type: ["string", "number"] }
|
|
54672
|
+
}
|
|
54673
|
+
]
|
|
54674
|
+
}
|
|
54675
|
+
],
|
|
54676
|
+
minItems: 3,
|
|
54677
|
+
maxItems: 3
|
|
54678
|
+
},
|
|
54679
|
+
{
|
|
54680
|
+
prefixItems: [
|
|
54681
|
+
{ type: "string", minLength: 1 },
|
|
54682
|
+
{ type: "string", minLength: 1 },
|
|
54683
|
+
{ type: "string", minLength: 1 },
|
|
54684
|
+
{
|
|
54685
|
+
oneOf: [
|
|
54686
|
+
{ type: ["string", "number", "boolean", "null"] },
|
|
54687
|
+
{
|
|
54688
|
+
type: "array",
|
|
54689
|
+
items: { type: ["string", "number"] }
|
|
54690
|
+
}
|
|
54691
|
+
]
|
|
54692
|
+
}
|
|
54693
|
+
],
|
|
54694
|
+
minItems: 4,
|
|
54695
|
+
maxItems: 4
|
|
54696
|
+
},
|
|
54697
|
+
{
|
|
54698
|
+
// 3.0.x advertised arbitrary string arrays. Keep accepting
|
|
54699
|
+
// that legacy surface while describing real 3/4-part Frappe
|
|
54700
|
+
// tuples precisely for modern clients.
|
|
54701
|
+
items: { type: "string" }
|
|
54702
|
+
}
|
|
54703
|
+
]
|
|
54105
54704
|
}
|
|
54106
54705
|
},
|
|
54107
|
-
limit: {
|
|
54706
|
+
limit: {
|
|
54707
|
+
type: "integer",
|
|
54708
|
+
minimum: 1,
|
|
54709
|
+
maximum: DOC_LIST_MAX_LIMIT,
|
|
54710
|
+
description: "Max results (default 20, maximum 5000)"
|
|
54711
|
+
},
|
|
54712
|
+
offset: {
|
|
54713
|
+
type: "integer",
|
|
54714
|
+
minimum: 0,
|
|
54715
|
+
maximum: DOC_LIST_MAX_OFFSET,
|
|
54716
|
+
description: "Pagination offset (default 0, maximum 100000)"
|
|
54717
|
+
},
|
|
54718
|
+
skip_cache: {
|
|
54719
|
+
type: "boolean",
|
|
54720
|
+
description: "Force a fresh ERPNext read instead of using the short-lived list cache"
|
|
54721
|
+
},
|
|
54108
54722
|
order_by: {
|
|
54109
54723
|
type: "string",
|
|
54110
54724
|
description: "Order by clause (e.g. 'modified desc', 'name asc')"
|
|
@@ -54116,16 +54730,34 @@ var operationsTools = [
|
|
|
54116
54730
|
if (!input.doctype) {
|
|
54117
54731
|
throw new Error("[erpnext_doc_list] 'doctype' is required");
|
|
54118
54732
|
}
|
|
54733
|
+
if (input.limit !== void 0 && (typeof input.limit !== "number" || !Number.isInteger(input.limit) || input.limit < 1 || input.limit > DOC_LIST_MAX_LIMIT)) {
|
|
54734
|
+
throw new Error(
|
|
54735
|
+
`[erpnext_doc_list] 'limit' must be an integer between 1 and ${DOC_LIST_MAX_LIMIT}`
|
|
54736
|
+
);
|
|
54737
|
+
}
|
|
54738
|
+
if (input.offset !== void 0 && (typeof input.offset !== "number" || !Number.isInteger(input.offset) || input.offset < 0 || input.offset > DOC_LIST_MAX_OFFSET)) {
|
|
54739
|
+
throw new Error(
|
|
54740
|
+
`[erpnext_doc_list] 'offset' must be an integer between 0 and ${DOC_LIST_MAX_OFFSET}`
|
|
54741
|
+
);
|
|
54742
|
+
}
|
|
54743
|
+
if (input.skip_cache !== void 0 && typeof input.skip_cache !== "boolean") {
|
|
54744
|
+
throw new Error("[erpnext_doc_list] 'skip_cache' must be a boolean");
|
|
54745
|
+
}
|
|
54119
54746
|
const limit = input.limit ?? 20;
|
|
54120
54747
|
const fields = input.fields ?? ["name", "modified"];
|
|
54121
54748
|
const filters = input.filters ?? [];
|
|
54122
54749
|
const order_by = input.order_by ?? "modified desc";
|
|
54123
|
-
const
|
|
54750
|
+
const offset = input.offset;
|
|
54751
|
+
const listOptions = {
|
|
54124
54752
|
fields,
|
|
54125
54753
|
filters,
|
|
54126
54754
|
limit,
|
|
54755
|
+
...offset !== void 0 ? { limit_start: offset } : {},
|
|
54127
54756
|
order_by
|
|
54128
|
-
}
|
|
54757
|
+
};
|
|
54758
|
+
const docs = input.skip_cache === true ? await ctx.client.list(input.doctype, listOptions, {
|
|
54759
|
+
skipCache: true
|
|
54760
|
+
}) : await ctx.client.list(input.doctype, listOptions);
|
|
54129
54761
|
return {
|
|
54130
54762
|
doctype: input.doctype,
|
|
54131
54763
|
count: docs.length,
|
|
@@ -54368,11 +55000,11 @@ var setupTools = [
|
|
|
54368
55000
|
filters.push(["enabled", "=", 1]);
|
|
54369
55001
|
}
|
|
54370
55002
|
if (input.search) {
|
|
54371
|
-
const
|
|
55003
|
+
const literal3 = input.search.replace(
|
|
54372
55004
|
/[\\%_]/g,
|
|
54373
55005
|
(match2) => `\\${match2}`
|
|
54374
55006
|
);
|
|
54375
|
-
filters.push(["full_name", "like", `%${
|
|
55007
|
+
filters.push(["full_name", "like", `%${literal3}%`]);
|
|
54376
55008
|
}
|
|
54377
55009
|
const docs = await ctx.client.list("User", {
|
|
54378
55010
|
fields: ["name", "full_name", "enabled"],
|
|
@@ -54477,14 +55109,14 @@ var setupTools = [
|
|
|
54477
55109
|
// src/tools/analytics.ts
|
|
54478
55110
|
var MAX_RADAR_ITEMS = 8;
|
|
54479
55111
|
function monthlyLabels(now, count) {
|
|
54480
|
-
|
|
54481
|
-
|
|
54482
|
-
|
|
54483
|
-
|
|
54484
|
-
|
|
55112
|
+
const labels = [];
|
|
55113
|
+
for (let offset = count - 1; offset >= 0; offset--) {
|
|
55114
|
+
const month = new Date(now.getFullYear(), now.getMonth() - offset, 1);
|
|
55115
|
+
labels.push(
|
|
55116
|
+
`${month.toLocaleString("en", { month: "short" })} ${month.getFullYear().toString().slice(2)}`
|
|
54485
55117
|
);
|
|
54486
|
-
|
|
54487
|
-
|
|
55118
|
+
}
|
|
55119
|
+
return labels;
|
|
54488
55120
|
}
|
|
54489
55121
|
function frappeCalendarMonth(value) {
|
|
54490
55122
|
if (typeof value !== "string") return null;
|
|
@@ -54615,7 +55247,6 @@ var analyticsTools = [
|
|
|
54615
55247
|
const invoices2 = await ctx.client.list("Sales Invoice", {
|
|
54616
55248
|
fields: ["name", "status", "grand_total"],
|
|
54617
55249
|
filters: [["docstatus", "!=", 2]],
|
|
54618
|
-
// exclude cancelled
|
|
54619
55250
|
limit: 500,
|
|
54620
55251
|
order_by: "modified desc"
|
|
54621
55252
|
});
|
|
@@ -54746,17 +55377,7 @@ var analyticsTools = [
|
|
|
54746
55377
|
limit: 1e3,
|
|
54747
55378
|
order_by: "transaction_date asc"
|
|
54748
55379
|
});
|
|
54749
|
-
const months =
|
|
54750
|
-
for (let m = 0; m < monthsBack; m++) {
|
|
54751
|
-
const d = new Date(
|
|
54752
|
-
now.getFullYear(),
|
|
54753
|
-
now.getMonth() - monthsBack + 1 + m,
|
|
54754
|
-
1
|
|
54755
|
-
);
|
|
54756
|
-
months.push(
|
|
54757
|
-
`${d.toLocaleString("en", { month: "short" })} ${d.getFullYear().toString().slice(2)}`
|
|
54758
|
-
);
|
|
54759
|
-
}
|
|
55380
|
+
const months = monthlyLabels(now, monthsBack);
|
|
54760
55381
|
if (groupBy === "customer") {
|
|
54761
55382
|
const byCustomerMonth = {};
|
|
54762
55383
|
for (const order2 of orders) {
|
|
@@ -54953,6 +55574,7 @@ var analyticsTools = [
|
|
|
54953
55574
|
color: "#fbbf24",
|
|
54954
55575
|
type: "line",
|
|
54955
55576
|
yAxisId: "right",
|
|
55577
|
+
unit: "orders",
|
|
54956
55578
|
showDots: true
|
|
54957
55579
|
}
|
|
54958
55580
|
],
|
|
@@ -55236,6 +55858,7 @@ var analyticsTools = [
|
|
|
55236
55858
|
limit: 5e3
|
|
55237
55859
|
});
|
|
55238
55860
|
const sparkline = [0, 0, 0, 0, 0, 0];
|
|
55861
|
+
const sparklineLabels = monthlyLabels(now, sparkline.length);
|
|
55239
55862
|
for (const o of allOrders) {
|
|
55240
55863
|
const calendarMonth = frappeCalendarMonth(o.transaction_date);
|
|
55241
55864
|
if (!calendarMonth) continue;
|
|
@@ -55257,7 +55880,7 @@ var analyticsTools = [
|
|
|
55257
55880
|
trend: delta > 0 ? "up" : delta < 0 ? "down" : "flat",
|
|
55258
55881
|
trendIsGood: true,
|
|
55259
55882
|
sparkline,
|
|
55260
|
-
sparklineLabels
|
|
55883
|
+
sparklineLabels,
|
|
55261
55884
|
color: "#60a5fa",
|
|
55262
55885
|
_meta: KPI_META
|
|
55263
55886
|
};
|
|
@@ -55410,7 +56033,9 @@ var analyticsTools = [
|
|
|
55410
56033
|
category: "analytics",
|
|
55411
56034
|
inputSchema: { type: "object", properties: {} },
|
|
55412
56035
|
handler: async (_input, ctx) => {
|
|
55413
|
-
const
|
|
56036
|
+
const now = /* @__PURE__ */ new Date();
|
|
56037
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
56038
|
+
const today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
55414
56039
|
const invoices = await ctx.client.list("Sales Invoice", {
|
|
55415
56040
|
fields: ["outstanding_amount", "due_date"],
|
|
55416
56041
|
filters: [
|
|
@@ -56890,6 +57515,1350 @@ var kanbanTools = [
|
|
|
56890
57515
|
}
|
|
56891
57516
|
];
|
|
56892
57517
|
|
|
57518
|
+
// src/buy/identities.ts
|
|
57519
|
+
var BUY_SOURCE_CAPTURE_SCHEMA = "io.casys.mcp-erpnext.buy-source-capture/1.0";
|
|
57520
|
+
var BUY_RECORDED_RESULT_SCHEMA = "io.casys.mcp-erpnext.buy-recorded-result/1.0";
|
|
57521
|
+
var BUY_RECORDED_RESULT_SCHEMA_V2 = "io.casys.mcp-erpnext.buy-recorded-result/2.0";
|
|
57522
|
+
var BUY_RECORDED_SESSION_SCHEMA = "io.casys.mcp-erpnext.buy-recorded-session/1.0";
|
|
57523
|
+
var BUY_EVIDENCE_VIEWER_URI = "ui://mcp-erpnext/buy-evidence-viewer";
|
|
57524
|
+
var BUY_VIEW_APP_MANIFEST_URI = "ui://mcp-erpnext/buy-evidence-manifest";
|
|
57525
|
+
var BUY_VIEW_APP_ID = "io.casys.mcp-erpnext.buy-evidence";
|
|
57526
|
+
var BUY_VIEW_APP_TITLE = "ERPNext Buy Evidence";
|
|
57527
|
+
var BUY_VIEW_APP_VERSION = "3.2.0-beta.1";
|
|
57528
|
+
var BUY_SOURCE_INSTANCE_KIND = "erpnext-site";
|
|
57529
|
+
var BUY_CAPTURE_DOCTYPE = [
|
|
57530
|
+
"Item",
|
|
57531
|
+
"BOM",
|
|
57532
|
+
"Item Price",
|
|
57533
|
+
"Supplier Quotation",
|
|
57534
|
+
"Supplier",
|
|
57535
|
+
"Price List",
|
|
57536
|
+
"UOM",
|
|
57537
|
+
"Currency Exchange"
|
|
57538
|
+
];
|
|
57539
|
+
var BUY_CAPTURE_MAX_DOCUMENTS = 32;
|
|
57540
|
+
var BUY_CAPTURE_MAX_CHILD_ROWS = 200;
|
|
57541
|
+
var BUY_CAPTURE_MAX_CANONICAL_BYTES = 262144;
|
|
57542
|
+
var BUY_CAPTURE_MAX_NAME_LENGTH = 140;
|
|
57543
|
+
var BUY_DOCTYPE_SOURCE_CATEGORY = {
|
|
57544
|
+
Item: "item",
|
|
57545
|
+
BOM: "bom",
|
|
57546
|
+
"Item Price": "catalogue-price",
|
|
57547
|
+
"Supplier Quotation": "supplier-quotation",
|
|
57548
|
+
Supplier: "supplier",
|
|
57549
|
+
"Price List": "price-list",
|
|
57550
|
+
UOM: "uom",
|
|
57551
|
+
"Currency Exchange": "currency-exchange"
|
|
57552
|
+
};
|
|
57553
|
+
var BUY_CONSISTENCY_KIND = "repeated-read";
|
|
57554
|
+
var BUY_CONSISTENCY_READS = 2;
|
|
57555
|
+
|
|
57556
|
+
// src/buy/errors.ts
|
|
57557
|
+
var BuyCaptureError = class extends Error {
|
|
57558
|
+
code;
|
|
57559
|
+
retryable;
|
|
57560
|
+
context;
|
|
57561
|
+
recovery;
|
|
57562
|
+
constructor(code, message2, options) {
|
|
57563
|
+
super(message2);
|
|
57564
|
+
this.name = "BuyCaptureError";
|
|
57565
|
+
this.code = code;
|
|
57566
|
+
this.retryable = options.retryable === true;
|
|
57567
|
+
this.context = Object.freeze({ ...options.context ?? {} });
|
|
57568
|
+
this.recovery = options.recovery;
|
|
57569
|
+
}
|
|
57570
|
+
};
|
|
57571
|
+
|
|
57572
|
+
// src/buy/json.ts
|
|
57573
|
+
var FINGERPRINT = /^sha256:[a-f0-9]{64}$/;
|
|
57574
|
+
var ISO_MILLISECONDS = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/;
|
|
57575
|
+
var CALENDAR_DATE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
|
57576
|
+
var FRAPPE_DATETIME = /^[0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?$/;
|
|
57577
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
57578
|
+
"api_key",
|
|
57579
|
+
"api_secret",
|
|
57580
|
+
"authorization",
|
|
57581
|
+
"baseUrl",
|
|
57582
|
+
"command",
|
|
57583
|
+
"commands",
|
|
57584
|
+
"cookie",
|
|
57585
|
+
"credentials",
|
|
57586
|
+
"endpoint",
|
|
57587
|
+
"password",
|
|
57588
|
+
"source_path",
|
|
57589
|
+
"sourcePath",
|
|
57590
|
+
"stagedPath"
|
|
57591
|
+
]);
|
|
57592
|
+
function record2(value, name) {
|
|
57593
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
57594
|
+
throw new TypeError(`${name} must be an object.`);
|
|
57595
|
+
}
|
|
57596
|
+
return value;
|
|
57597
|
+
}
|
|
57598
|
+
function exactKeys(value, keys, name) {
|
|
57599
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
57600
|
+
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
57601
|
+
throw new TypeError(`${name} contains missing or unsupported fields.`);
|
|
57602
|
+
}
|
|
57603
|
+
for (const key of ownKeys) {
|
|
57604
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
57605
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) {
|
|
57606
|
+
throw new TypeError(`${name} contains missing or unsupported fields.`);
|
|
57607
|
+
}
|
|
57608
|
+
}
|
|
57609
|
+
const actual = ownKeys.toSorted();
|
|
57610
|
+
const expected = [...keys].toSorted();
|
|
57611
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
57612
|
+
throw new TypeError(`${name} contains missing or unsupported fields.`);
|
|
57613
|
+
}
|
|
57614
|
+
}
|
|
57615
|
+
function exactRecord(value, keys, name) {
|
|
57616
|
+
const root = record2(value, name);
|
|
57617
|
+
exactKeys(root, keys, name);
|
|
57618
|
+
rejectForbiddenKeys(root, name);
|
|
57619
|
+
return root;
|
|
57620
|
+
}
|
|
57621
|
+
function denseArray(value, name) {
|
|
57622
|
+
if (!Array.isArray(value)) throw new TypeError(`${name} must be an array.`);
|
|
57623
|
+
const allowed = /* @__PURE__ */ new Set(["length"]);
|
|
57624
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
57625
|
+
allowed.add(String(index));
|
|
57626
|
+
}
|
|
57627
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
57628
|
+
if (ownKeys.length !== allowed.size || ownKeys.some((key) => typeof key !== "string" || !allowed.has(key))) {
|
|
57629
|
+
throw new TypeError(`${name} must be a dense, unadorned array.`);
|
|
57630
|
+
}
|
|
57631
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
57632
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
57633
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) {
|
|
57634
|
+
throw new TypeError(`${name} must be a dense, unadorned array.`);
|
|
57635
|
+
}
|
|
57636
|
+
}
|
|
57637
|
+
return value;
|
|
57638
|
+
}
|
|
57639
|
+
function literal2(value, expected, name) {
|
|
57640
|
+
if (value !== expected) throw new TypeError(`${name} must be ${expected}.`);
|
|
57641
|
+
return expected;
|
|
57642
|
+
}
|
|
57643
|
+
function oneOf(value, allowed, name) {
|
|
57644
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
57645
|
+
throw new TypeError(`${name} must be one of ${allowed.join(", ")}.`);
|
|
57646
|
+
}
|
|
57647
|
+
return value;
|
|
57648
|
+
}
|
|
57649
|
+
function nonEmpty(value, name) {
|
|
57650
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
57651
|
+
throw new TypeError(`${name} must be a non-empty string.`);
|
|
57652
|
+
}
|
|
57653
|
+
if (value.trim() !== value) {
|
|
57654
|
+
throw new TypeError(`${name} must not contain whitespace padding.`);
|
|
57655
|
+
}
|
|
57656
|
+
return value;
|
|
57657
|
+
}
|
|
57658
|
+
function pattern(value, expected, name) {
|
|
57659
|
+
if (typeof value !== "string" || !expected.test(value)) {
|
|
57660
|
+
throw new TypeError(`${name} has an invalid format.`);
|
|
57661
|
+
}
|
|
57662
|
+
return value;
|
|
57663
|
+
}
|
|
57664
|
+
function fingerprint(value, name) {
|
|
57665
|
+
return pattern(value, FINGERPRINT, name);
|
|
57666
|
+
}
|
|
57667
|
+
function canonicalTimestamp(value, name) {
|
|
57668
|
+
const timestamp = pattern(value, ISO_MILLISECONDS, name);
|
|
57669
|
+
try {
|
|
57670
|
+
if (new Date(timestamp).toISOString() !== timestamp) {
|
|
57671
|
+
throw new TypeError(`${name} is not canonical UTC.`);
|
|
57672
|
+
}
|
|
57673
|
+
} catch {
|
|
57674
|
+
throw new TypeError(`${name} is not canonical UTC.`);
|
|
57675
|
+
}
|
|
57676
|
+
return timestamp;
|
|
57677
|
+
}
|
|
57678
|
+
function calendarDate(value, name) {
|
|
57679
|
+
const date4 = pattern(value, CALENDAR_DATE, name);
|
|
57680
|
+
const parsed = /* @__PURE__ */ new Date(`${date4}T00:00:00.000Z`);
|
|
57681
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== date4) {
|
|
57682
|
+
throw new TypeError(`${name} is not a real calendar date.`);
|
|
57683
|
+
}
|
|
57684
|
+
return date4;
|
|
57685
|
+
}
|
|
57686
|
+
function frappeDatetime(value, name) {
|
|
57687
|
+
const datetime3 = pattern(value, FRAPPE_DATETIME, name);
|
|
57688
|
+
calendarDate(datetime3.slice(0, 10), name);
|
|
57689
|
+
const [hour, minute, second] = datetime3.slice(11, 19).split(":").map(Number);
|
|
57690
|
+
if (hour > 23 || minute > 59 || second > 59) {
|
|
57691
|
+
throw new TypeError(`${name} is not a real Frappe datetime.`);
|
|
57692
|
+
}
|
|
57693
|
+
return datetime3;
|
|
57694
|
+
}
|
|
57695
|
+
function boundedArray(value, max, name) {
|
|
57696
|
+
const items = denseArray(value, name);
|
|
57697
|
+
if (items.length > max) {
|
|
57698
|
+
throw new TypeError(`${name} exceeds the ${max}-entry bound.`);
|
|
57699
|
+
}
|
|
57700
|
+
return items;
|
|
57701
|
+
}
|
|
57702
|
+
function rejectForbiddenKeys(value, name) {
|
|
57703
|
+
if (Array.isArray(value)) {
|
|
57704
|
+
const items = denseArray(value, name);
|
|
57705
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
57706
|
+
rejectForbiddenKeys(items[index], `${name}[${index}]`);
|
|
57707
|
+
}
|
|
57708
|
+
return;
|
|
57709
|
+
}
|
|
57710
|
+
if (typeof value !== "object" || value === null) return;
|
|
57711
|
+
const root = record2(value, name);
|
|
57712
|
+
for (const key of Object.keys(root)) {
|
|
57713
|
+
if (FORBIDDEN_KEYS.has(key)) {
|
|
57714
|
+
throw new TypeError(
|
|
57715
|
+
`${name}.${key} is a private credential, path, or command field and must not appear in the Buy contract.`
|
|
57716
|
+
);
|
|
57717
|
+
}
|
|
57718
|
+
rejectForbiddenKeys(root[key], `${name}.${key}`);
|
|
57719
|
+
}
|
|
57720
|
+
}
|
|
57721
|
+
function canonicalJson2(value, name) {
|
|
57722
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
57723
|
+
return JSON.stringify(value);
|
|
57724
|
+
}
|
|
57725
|
+
if (typeof value === "number") {
|
|
57726
|
+
if (!Number.isFinite(value)) {
|
|
57727
|
+
throw new TypeError(`${name} must contain only finite JSON numbers.`);
|
|
57728
|
+
}
|
|
57729
|
+
return JSON.stringify(value);
|
|
57730
|
+
}
|
|
57731
|
+
if (Array.isArray(value)) {
|
|
57732
|
+
const items = denseArray(value, name);
|
|
57733
|
+
return `[${items.map((item, index) => canonicalJson2(item, `${name}[${index}]`)).join(",")}]`;
|
|
57734
|
+
}
|
|
57735
|
+
const root = record2(value, name);
|
|
57736
|
+
const prototype = Object.getPrototypeOf(root);
|
|
57737
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
57738
|
+
throw new TypeError(`${name} must contain only plain JSON objects.`);
|
|
57739
|
+
}
|
|
57740
|
+
const ownKeys = Reflect.ownKeys(root);
|
|
57741
|
+
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
57742
|
+
throw new TypeError(`${name} must contain only string-keyed JSON objects.`);
|
|
57743
|
+
}
|
|
57744
|
+
const keys = ownKeys.toSorted();
|
|
57745
|
+
const fields = keys.map((key) => {
|
|
57746
|
+
const descriptor = Object.getOwnPropertyDescriptor(root, key);
|
|
57747
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) {
|
|
57748
|
+
throw new TypeError(`${name}.${key} must be an enumerable JSON value.`);
|
|
57749
|
+
}
|
|
57750
|
+
if (descriptor.value === void 0) {
|
|
57751
|
+
throw new TypeError(`${name}.${key} must not be undefined.`);
|
|
57752
|
+
}
|
|
57753
|
+
return `${JSON.stringify(key)}:${canonicalJson2(descriptor.value, `${name}.${key}`)}`;
|
|
57754
|
+
});
|
|
57755
|
+
return `{${fields.join(",")}}`;
|
|
57756
|
+
}
|
|
57757
|
+
function utf8ByteCount(text) {
|
|
57758
|
+
return new TextEncoder().encode(text).byteLength;
|
|
57759
|
+
}
|
|
57760
|
+
async function sha256HexOfUtf8(text) {
|
|
57761
|
+
const bytes = new TextEncoder().encode(text);
|
|
57762
|
+
const digestBytes = new Uint8Array(
|
|
57763
|
+
await crypto.subtle.digest("SHA-256", bytes)
|
|
57764
|
+
);
|
|
57765
|
+
return Array.from(digestBytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
57766
|
+
}
|
|
57767
|
+
async function sha256FingerprintOfUtf8(text) {
|
|
57768
|
+
return `sha256:${await sha256HexOfUtf8(text)}`;
|
|
57769
|
+
}
|
|
57770
|
+
|
|
57771
|
+
// src/buy/projection.ts
|
|
57772
|
+
var DECIMAL = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
|
|
57773
|
+
var CURRENCY = /^[A-Z]{3}$/;
|
|
57774
|
+
var SENSITIVE_FIELDS = /* @__PURE__ */ new Set([
|
|
57775
|
+
"api_key",
|
|
57776
|
+
"api_secret",
|
|
57777
|
+
"password",
|
|
57778
|
+
"email_id",
|
|
57779
|
+
"email",
|
|
57780
|
+
"mobile_no",
|
|
57781
|
+
"phone",
|
|
57782
|
+
"cell_number"
|
|
57783
|
+
]);
|
|
57784
|
+
var ITEM_FIELDS = [
|
|
57785
|
+
{ key: "item_code", kind: "string", required: true },
|
|
57786
|
+
{ key: "item_name", kind: "string" },
|
|
57787
|
+
{ key: "item_group", kind: "string" },
|
|
57788
|
+
{ key: "stock_uom", kind: "string" },
|
|
57789
|
+
{ key: "is_purchase_item", kind: "boolean" },
|
|
57790
|
+
{ key: "is_stock_item", kind: "boolean" },
|
|
57791
|
+
{ key: "disabled", kind: "boolean" }
|
|
57792
|
+
];
|
|
57793
|
+
var BOM_FIELDS = [
|
|
57794
|
+
{ key: "item", kind: "string", required: true },
|
|
57795
|
+
{ key: "item_name", kind: "string" },
|
|
57796
|
+
{ key: "quantity", kind: "decimal", required: true },
|
|
57797
|
+
{ key: "uom", kind: "string", required: true },
|
|
57798
|
+
{ key: "is_active", kind: "boolean" },
|
|
57799
|
+
{ key: "is_default", kind: "boolean" },
|
|
57800
|
+
{ key: "company", kind: "string" },
|
|
57801
|
+
{ key: "currency", kind: "currency" },
|
|
57802
|
+
{ key: "with_operations", kind: "boolean" }
|
|
57803
|
+
];
|
|
57804
|
+
var BOM_ITEM_FIELDS = [
|
|
57805
|
+
{ key: "item_code", kind: "string", required: true },
|
|
57806
|
+
{ key: "item_name", kind: "string" },
|
|
57807
|
+
{ key: "qty", kind: "decimal", required: true },
|
|
57808
|
+
{ key: "uom", kind: "string", required: true },
|
|
57809
|
+
{ key: "rate", kind: "decimal" },
|
|
57810
|
+
{ key: "amount", kind: "decimal" },
|
|
57811
|
+
{ key: "sourced_by_supplier", kind: "boolean" }
|
|
57812
|
+
];
|
|
57813
|
+
var BOM_OPERATION_FIELDS = [
|
|
57814
|
+
{ key: "operation", kind: "string", required: true },
|
|
57815
|
+
{ key: "time_in_mins", kind: "decimal" },
|
|
57816
|
+
{ key: "hour_rate", kind: "decimal" }
|
|
57817
|
+
];
|
|
57818
|
+
var ITEM_PRICE_FIELDS = [
|
|
57819
|
+
{ key: "item_code", kind: "string", required: true },
|
|
57820
|
+
{ key: "item_name", kind: "string" },
|
|
57821
|
+
{ key: "price_list", kind: "string" },
|
|
57822
|
+
{ key: "price_list_rate", kind: "decimal", required: true },
|
|
57823
|
+
{ key: "currency", kind: "currency", required: true },
|
|
57824
|
+
{ key: "uom", kind: "string" },
|
|
57825
|
+
{ key: "min_qty", kind: "decimal" },
|
|
57826
|
+
{ key: "valid_from", kind: "date" },
|
|
57827
|
+
{ key: "valid_upto", kind: "date" },
|
|
57828
|
+
{ key: "supplier", kind: "string" },
|
|
57829
|
+
{ key: "buying", kind: "boolean" },
|
|
57830
|
+
{ key: "selling", kind: "boolean" }
|
|
57831
|
+
];
|
|
57832
|
+
var SUPPLIER_QUOTATION_FIELDS = [
|
|
57833
|
+
{ key: "supplier", kind: "string", required: true },
|
|
57834
|
+
{ key: "supplier_name", kind: "string" },
|
|
57835
|
+
{ key: "transaction_date", kind: "date", required: true },
|
|
57836
|
+
{ key: "valid_till", kind: "date" },
|
|
57837
|
+
{ key: "currency", kind: "currency", required: true },
|
|
57838
|
+
{ key: "conversion_rate", kind: "decimal" },
|
|
57839
|
+
{ key: "status", kind: "string" },
|
|
57840
|
+
{ key: "company", kind: "string" },
|
|
57841
|
+
{ key: "price_list", kind: "string" }
|
|
57842
|
+
];
|
|
57843
|
+
var SUPPLIER_QUOTATION_ITEM_FIELDS = [
|
|
57844
|
+
{ key: "item_code", kind: "string", required: true },
|
|
57845
|
+
{ key: "item_name", kind: "string" },
|
|
57846
|
+
{ key: "qty", kind: "decimal", required: true },
|
|
57847
|
+
{ key: "uom", kind: "string", required: true },
|
|
57848
|
+
{ key: "rate", kind: "decimal" },
|
|
57849
|
+
{ key: "amount", kind: "decimal" },
|
|
57850
|
+
{ key: "price_list_rate", kind: "decimal" },
|
|
57851
|
+
{ key: "discount_percentage", kind: "decimal" },
|
|
57852
|
+
{ key: "supplier_part_no", kind: "string" }
|
|
57853
|
+
];
|
|
57854
|
+
var SUPPLIER_FIELDS = [
|
|
57855
|
+
{ key: "supplier_name", kind: "string", required: true },
|
|
57856
|
+
{ key: "supplier_group", kind: "string" },
|
|
57857
|
+
{ key: "supplier_type", kind: "string" },
|
|
57858
|
+
{ key: "country", kind: "string" },
|
|
57859
|
+
{ key: "default_currency", kind: "currency" },
|
|
57860
|
+
{ key: "disabled", kind: "boolean" }
|
|
57861
|
+
];
|
|
57862
|
+
var PRICE_LIST_FIELDS = [
|
|
57863
|
+
{ key: "currency", kind: "currency", required: true },
|
|
57864
|
+
{ key: "enabled", kind: "boolean" },
|
|
57865
|
+
{ key: "buying", kind: "boolean" },
|
|
57866
|
+
{ key: "selling", kind: "boolean" },
|
|
57867
|
+
{ key: "price_not_uom_dependent", kind: "boolean" }
|
|
57868
|
+
];
|
|
57869
|
+
var UOM_FIELDS = [
|
|
57870
|
+
{ key: "uom_name", kind: "string" },
|
|
57871
|
+
{ key: "must_be_whole_number", kind: "boolean" }
|
|
57872
|
+
];
|
|
57873
|
+
var CURRENCY_EXCHANGE_FIELDS = [
|
|
57874
|
+
{ key: "from_currency", kind: "currency", required: true },
|
|
57875
|
+
{ key: "to_currency", kind: "currency", required: true },
|
|
57876
|
+
{ key: "exchange_rate", kind: "decimal", required: true },
|
|
57877
|
+
{ key: "date", kind: "date", required: true }
|
|
57878
|
+
];
|
|
57879
|
+
var BUY_DOCTYPE_CHILD_TABLES = {
|
|
57880
|
+
BOM: [
|
|
57881
|
+
{ table: "items", fields: BOM_ITEM_FIELDS },
|
|
57882
|
+
{ table: "operations", fields: BOM_OPERATION_FIELDS }
|
|
57883
|
+
],
|
|
57884
|
+
"Supplier Quotation": [
|
|
57885
|
+
{ table: "items", fields: SUPPLIER_QUOTATION_ITEM_FIELDS }
|
|
57886
|
+
]
|
|
57887
|
+
};
|
|
57888
|
+
var BUY_DOCTYPE_FIELD_SPECS = {
|
|
57889
|
+
Item: ITEM_FIELDS,
|
|
57890
|
+
BOM: BOM_FIELDS,
|
|
57891
|
+
"Item Price": ITEM_PRICE_FIELDS,
|
|
57892
|
+
"Supplier Quotation": SUPPLIER_QUOTATION_FIELDS,
|
|
57893
|
+
Supplier: SUPPLIER_FIELDS,
|
|
57894
|
+
"Price List": PRICE_LIST_FIELDS,
|
|
57895
|
+
UOM: UOM_FIELDS,
|
|
57896
|
+
"Currency Exchange": CURRENCY_EXCHANGE_FIELDS
|
|
57897
|
+
};
|
|
57898
|
+
function isBuyCaptureDoctype(value) {
|
|
57899
|
+
return BUY_CAPTURE_DOCTYPE.includes(value);
|
|
57900
|
+
}
|
|
57901
|
+
async function projectBuyDocument(doctype, raw2) {
|
|
57902
|
+
const name = requiredName(raw2.name, `${doctype}.name`);
|
|
57903
|
+
const modified = requiredModified(raw2.modified, `${doctype}.modified`);
|
|
57904
|
+
const fields = projectFields(
|
|
57905
|
+
raw2,
|
|
57906
|
+
BUY_DOCTYPE_FIELD_SPECS[doctype],
|
|
57907
|
+
`${doctype}`
|
|
57908
|
+
);
|
|
57909
|
+
const children = projectChildren(doctype, raw2);
|
|
57910
|
+
const docstatus = optionalDocstatus(raw2.docstatus, `${doctype}.docstatus`);
|
|
57911
|
+
const status = optionalStatus(raw2.status, `${doctype}.status`);
|
|
57912
|
+
const identity = {
|
|
57913
|
+
doctype,
|
|
57914
|
+
name,
|
|
57915
|
+
modified,
|
|
57916
|
+
...docstatus !== void 0 ? { docstatus } : {},
|
|
57917
|
+
...status !== void 0 ? { status } : {},
|
|
57918
|
+
sourceCategory: BUY_DOCTYPE_SOURCE_CATEGORY[doctype],
|
|
57919
|
+
fields,
|
|
57920
|
+
...children !== void 0 ? { children } : {}
|
|
57921
|
+
};
|
|
57922
|
+
const fingerprint2 = await sha256FingerprintOfUtf8(
|
|
57923
|
+
canonicalJson2(identity, `${doctype}:${name} projection`)
|
|
57924
|
+
);
|
|
57925
|
+
return { ...identity, fingerprint: fingerprint2 };
|
|
57926
|
+
}
|
|
57927
|
+
function projectChildren(doctype, raw2) {
|
|
57928
|
+
const specs = BUY_DOCTYPE_CHILD_TABLES[doctype];
|
|
57929
|
+
if (!specs) return void 0;
|
|
57930
|
+
const tables = [];
|
|
57931
|
+
for (const spec of specs) {
|
|
57932
|
+
const value = raw2[spec.table];
|
|
57933
|
+
if (value === void 0 || value === null) continue;
|
|
57934
|
+
if (!Array.isArray(value)) {
|
|
57935
|
+
throw new BuyCaptureError(
|
|
57936
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
57937
|
+
`${doctype}.${spec.table} must be a child table array.`,
|
|
57938
|
+
{
|
|
57939
|
+
recovery: "Capture only closed child tables with exact row identities.",
|
|
57940
|
+
context: { doctype, table: spec.table }
|
|
57941
|
+
}
|
|
57942
|
+
);
|
|
57943
|
+
}
|
|
57944
|
+
if (value.length > BUY_CAPTURE_MAX_CHILD_ROWS) {
|
|
57945
|
+
throw new BuyCaptureError(
|
|
57946
|
+
"BUY_CAPTURE_BOUNDS_EXCEEDED",
|
|
57947
|
+
`${doctype}.${spec.table} exceeds the ${BUY_CAPTURE_MAX_CHILD_ROWS}-row bound.`,
|
|
57948
|
+
{
|
|
57949
|
+
recovery: "Narrow the capture to fewer child rows.",
|
|
57950
|
+
context: {
|
|
57951
|
+
doctype,
|
|
57952
|
+
table: spec.table,
|
|
57953
|
+
limit: BUY_CAPTURE_MAX_CHILD_ROWS,
|
|
57954
|
+
actual: value.length
|
|
57955
|
+
}
|
|
57956
|
+
}
|
|
57957
|
+
);
|
|
57958
|
+
}
|
|
57959
|
+
if (value.length === 0) continue;
|
|
57960
|
+
const rows = value.map(
|
|
57961
|
+
(row, index) => projectChildRow(
|
|
57962
|
+
row,
|
|
57963
|
+
spec.fields,
|
|
57964
|
+
`${doctype}.${spec.table}[${index}]`
|
|
57965
|
+
)
|
|
57966
|
+
);
|
|
57967
|
+
tables.push({ table: spec.table, rows });
|
|
57968
|
+
}
|
|
57969
|
+
return tables.length > 0 ? tables : void 0;
|
|
57970
|
+
}
|
|
57971
|
+
function projectChildRow(row, specs, path) {
|
|
57972
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
57973
|
+
throw new BuyCaptureError(
|
|
57974
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
57975
|
+
`${path} must be a child row object.`,
|
|
57976
|
+
{
|
|
57977
|
+
recovery: "Child rows must keep their exact Frappe name identity.",
|
|
57978
|
+
context: { path }
|
|
57979
|
+
}
|
|
57980
|
+
);
|
|
57981
|
+
}
|
|
57982
|
+
const record3 = row;
|
|
57983
|
+
const name = requiredName(record3.name, `${path}.name`);
|
|
57984
|
+
const idx = optionalIdx(record3.idx, `${path}.idx`);
|
|
57985
|
+
return {
|
|
57986
|
+
name,
|
|
57987
|
+
...idx !== void 0 ? { idx } : {},
|
|
57988
|
+
fields: projectFields(record3, specs, path)
|
|
57989
|
+
};
|
|
57990
|
+
}
|
|
57991
|
+
function projectFields(raw2, specs, path) {
|
|
57992
|
+
for (const key of Object.keys(raw2)) {
|
|
57993
|
+
if (SENSITIVE_FIELDS.has(key)) {
|
|
57994
|
+
continue;
|
|
57995
|
+
}
|
|
57996
|
+
}
|
|
57997
|
+
const fields = {};
|
|
57998
|
+
for (const spec of specs) {
|
|
57999
|
+
const value = projectField(raw2[spec.key], spec, `${path}.${spec.key}`);
|
|
58000
|
+
if (value !== void 0) fields[spec.key] = value;
|
|
58001
|
+
}
|
|
58002
|
+
return fields;
|
|
58003
|
+
}
|
|
58004
|
+
function projectField(value, spec, path) {
|
|
58005
|
+
if (value === void 0 || value === null || value === "") {
|
|
58006
|
+
if (spec.required) {
|
|
58007
|
+
throw new BuyCaptureError(
|
|
58008
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58009
|
+
`${path} is required on the closed commercial projection.`,
|
|
58010
|
+
{
|
|
58011
|
+
recovery: "The ERP document is missing a field later consumed as cost evidence.",
|
|
58012
|
+
context: { path, field: spec.key }
|
|
58013
|
+
}
|
|
58014
|
+
);
|
|
58015
|
+
}
|
|
58016
|
+
return void 0;
|
|
58017
|
+
}
|
|
58018
|
+
switch (spec.kind) {
|
|
58019
|
+
case "string":
|
|
58020
|
+
return requiredString(value, path);
|
|
58021
|
+
case "decimal":
|
|
58022
|
+
return requiredDecimal(value, path);
|
|
58023
|
+
case "boolean":
|
|
58024
|
+
return requiredBoolean(value, path);
|
|
58025
|
+
case "currency":
|
|
58026
|
+
return requiredCurrency(value, path);
|
|
58027
|
+
case "date":
|
|
58028
|
+
return requiredDate(value, path);
|
|
58029
|
+
case "integer":
|
|
58030
|
+
return requiredInteger(value, path);
|
|
58031
|
+
}
|
|
58032
|
+
}
|
|
58033
|
+
function requiredName(value, path) {
|
|
58034
|
+
const name = requiredString(value, path);
|
|
58035
|
+
if (name.length > BUY_CAPTURE_MAX_NAME_LENGTH) {
|
|
58036
|
+
throw new BuyCaptureError(
|
|
58037
|
+
"BUY_CAPTURE_BOUNDS_EXCEEDED",
|
|
58038
|
+
`${path} exceeds the ${BUY_CAPTURE_MAX_NAME_LENGTH}-character bound.`,
|
|
58039
|
+
{
|
|
58040
|
+
recovery: "Use the exact ERP document name; do not pass a description.",
|
|
58041
|
+
context: {
|
|
58042
|
+
path,
|
|
58043
|
+
limit: BUY_CAPTURE_MAX_NAME_LENGTH,
|
|
58044
|
+
actual: name.length
|
|
58045
|
+
}
|
|
58046
|
+
}
|
|
58047
|
+
);
|
|
58048
|
+
}
|
|
58049
|
+
return name;
|
|
58050
|
+
}
|
|
58051
|
+
function requiredModified(value, path) {
|
|
58052
|
+
try {
|
|
58053
|
+
return frappeDatetime(value, path);
|
|
58054
|
+
} catch {
|
|
58055
|
+
throw new BuyCaptureError(
|
|
58056
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58057
|
+
`${path} is not a Frappe datetime.`,
|
|
58058
|
+
{
|
|
58059
|
+
recovery: "Capture the document's exact modified timestamp.",
|
|
58060
|
+
context: { path }
|
|
58061
|
+
}
|
|
58062
|
+
);
|
|
58063
|
+
}
|
|
58064
|
+
}
|
|
58065
|
+
function requiredString(value, path) {
|
|
58066
|
+
if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
|
|
58067
|
+
throw new BuyCaptureError(
|
|
58068
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58069
|
+
`${path} must be a non-empty unpadded string.`,
|
|
58070
|
+
{
|
|
58071
|
+
recovery: "Use the exact ERP string; do not invent a label.",
|
|
58072
|
+
context: { path }
|
|
58073
|
+
}
|
|
58074
|
+
);
|
|
58075
|
+
}
|
|
58076
|
+
return value;
|
|
58077
|
+
}
|
|
58078
|
+
function requiredDecimal(value, path) {
|
|
58079
|
+
if (typeof value === "number") {
|
|
58080
|
+
if (!Number.isFinite(value)) {
|
|
58081
|
+
throw new BuyCaptureError(
|
|
58082
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58083
|
+
`${path} must be a finite decimal.`,
|
|
58084
|
+
{
|
|
58085
|
+
recovery: "Refuse non-finite commercial amounts rather than coerce them.",
|
|
58086
|
+
context: { path }
|
|
58087
|
+
}
|
|
58088
|
+
);
|
|
58089
|
+
}
|
|
58090
|
+
return JSON.stringify(value);
|
|
58091
|
+
}
|
|
58092
|
+
if (typeof value === "string") {
|
|
58093
|
+
if (!DECIMAL.test(value)) {
|
|
58094
|
+
throw new BuyCaptureError(
|
|
58095
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58096
|
+
`${path} must be a canonical decimal string.`,
|
|
58097
|
+
{
|
|
58098
|
+
recovery: "Keep the ERP decimal; do not fill absence with zero.",
|
|
58099
|
+
context: { path }
|
|
58100
|
+
}
|
|
58101
|
+
);
|
|
58102
|
+
}
|
|
58103
|
+
return value;
|
|
58104
|
+
}
|
|
58105
|
+
throw new BuyCaptureError(
|
|
58106
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58107
|
+
`${path} must be a decimal.`,
|
|
58108
|
+
{
|
|
58109
|
+
recovery: "Keep the ERP decimal; do not fill absence with zero.",
|
|
58110
|
+
context: { path }
|
|
58111
|
+
}
|
|
58112
|
+
);
|
|
58113
|
+
}
|
|
58114
|
+
function requiredBoolean(value, path) {
|
|
58115
|
+
if (value === true || value === 1) return true;
|
|
58116
|
+
if (value === false || value === 0) return false;
|
|
58117
|
+
throw new BuyCaptureError(
|
|
58118
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58119
|
+
`${path} must be a boolean or Frappe 0/1.`,
|
|
58120
|
+
{
|
|
58121
|
+
recovery: "Do not coerce missing flags to false.",
|
|
58122
|
+
context: { path }
|
|
58123
|
+
}
|
|
58124
|
+
);
|
|
58125
|
+
}
|
|
58126
|
+
function requiredCurrency(value, path) {
|
|
58127
|
+
const code = requiredString(value, path);
|
|
58128
|
+
if (!CURRENCY.test(code)) {
|
|
58129
|
+
throw new BuyCaptureError(
|
|
58130
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58131
|
+
`${path} must be a 3-letter currency code.`,
|
|
58132
|
+
{
|
|
58133
|
+
recovery: "Capture the document currency; do not guess a conversion.",
|
|
58134
|
+
context: { path }
|
|
58135
|
+
}
|
|
58136
|
+
);
|
|
58137
|
+
}
|
|
58138
|
+
return code;
|
|
58139
|
+
}
|
|
58140
|
+
function requiredDate(value, path) {
|
|
58141
|
+
try {
|
|
58142
|
+
return calendarDate(value, path);
|
|
58143
|
+
} catch {
|
|
58144
|
+
throw new BuyCaptureError(
|
|
58145
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58146
|
+
`${path} must be a real YYYY-MM-DD date.`,
|
|
58147
|
+
{
|
|
58148
|
+
recovery: "Keep the ERP calendar date; do not localize it.",
|
|
58149
|
+
context: { path }
|
|
58150
|
+
}
|
|
58151
|
+
);
|
|
58152
|
+
}
|
|
58153
|
+
}
|
|
58154
|
+
function requiredInteger(value, path) {
|
|
58155
|
+
if (!Number.isSafeInteger(value)) {
|
|
58156
|
+
throw new BuyCaptureError(
|
|
58157
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58158
|
+
`${path} must be a safe integer.`,
|
|
58159
|
+
{ recovery: "Keep the ERP integer identity.", context: { path } }
|
|
58160
|
+
);
|
|
58161
|
+
}
|
|
58162
|
+
return value;
|
|
58163
|
+
}
|
|
58164
|
+
function optionalDocstatus(value, path) {
|
|
58165
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
58166
|
+
if (value !== 0 && value !== 1 && value !== 2) {
|
|
58167
|
+
throw new BuyCaptureError(
|
|
58168
|
+
"BUY_CAPTURE_INVALID_PROJECTION",
|
|
58169
|
+
`${path} must be 0, 1, or 2.`,
|
|
58170
|
+
{ recovery: "Keep the ERP docstatus.", context: { path } }
|
|
58171
|
+
);
|
|
58172
|
+
}
|
|
58173
|
+
return value;
|
|
58174
|
+
}
|
|
58175
|
+
function optionalStatus(value, path) {
|
|
58176
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
58177
|
+
return requiredString(value, path);
|
|
58178
|
+
}
|
|
58179
|
+
function optionalIdx(value, path) {
|
|
58180
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
58181
|
+
return requiredInteger(value, path);
|
|
58182
|
+
}
|
|
58183
|
+
function parseClosedWireFields(value, specs, path) {
|
|
58184
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
58185
|
+
throw new TypeError(`${path} must be an object.`);
|
|
58186
|
+
}
|
|
58187
|
+
const root = value;
|
|
58188
|
+
const allowed = new Map(specs.map((spec) => [spec.key, spec]));
|
|
58189
|
+
for (const key of Object.keys(root)) {
|
|
58190
|
+
if (!allowed.has(key)) {
|
|
58191
|
+
throw new TypeError(`${path}.${key} is not a closed commercial field.`);
|
|
58192
|
+
}
|
|
58193
|
+
}
|
|
58194
|
+
const fields = {};
|
|
58195
|
+
for (const spec of specs) {
|
|
58196
|
+
const raw2 = root[spec.key];
|
|
58197
|
+
if (raw2 === void 0 || raw2 === null || raw2 === "") {
|
|
58198
|
+
if (spec.required) {
|
|
58199
|
+
throw new TypeError(`${path}.${spec.key} is required.`);
|
|
58200
|
+
}
|
|
58201
|
+
continue;
|
|
58202
|
+
}
|
|
58203
|
+
fields[spec.key] = parseWireValue(raw2, spec, `${path}.${spec.key}`);
|
|
58204
|
+
}
|
|
58205
|
+
return fields;
|
|
58206
|
+
}
|
|
58207
|
+
function parseClosedWireChildren(doctype, value, path) {
|
|
58208
|
+
if (value === void 0) return void 0;
|
|
58209
|
+
if (!Array.isArray(value)) {
|
|
58210
|
+
throw new TypeError(`${path} must be an array.`);
|
|
58211
|
+
}
|
|
58212
|
+
const specs = BUY_DOCTYPE_CHILD_TABLES[doctype] ?? [];
|
|
58213
|
+
if (specs.length === 0) {
|
|
58214
|
+
throw new TypeError(`${path} is not admitted for ${doctype}.`);
|
|
58215
|
+
}
|
|
58216
|
+
if (value.length === 0) {
|
|
58217
|
+
throw new TypeError(`${path} must be omitted when empty.`);
|
|
58218
|
+
}
|
|
58219
|
+
const known = new Map(specs.map((spec) => [spec.table, spec]));
|
|
58220
|
+
const seenTables = /* @__PURE__ */ new Set();
|
|
58221
|
+
return value.map((item, index) => {
|
|
58222
|
+
const tablePath = `${path}[${index}]`;
|
|
58223
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
58224
|
+
throw new TypeError(`${tablePath} must be an object.`);
|
|
58225
|
+
}
|
|
58226
|
+
const table2 = item;
|
|
58227
|
+
const keys = Object.keys(table2).toSorted();
|
|
58228
|
+
if (keys.length !== 2 || keys[0] !== "rows" || keys[1] !== "table") {
|
|
58229
|
+
throw new TypeError(
|
|
58230
|
+
`${tablePath} contains missing or unsupported fields.`
|
|
58231
|
+
);
|
|
58232
|
+
}
|
|
58233
|
+
if (typeof table2.table !== "string" || !known.has(table2.table)) {
|
|
58234
|
+
throw new TypeError(
|
|
58235
|
+
`${tablePath}.table is not a closed child table for ${doctype}.`
|
|
58236
|
+
);
|
|
58237
|
+
}
|
|
58238
|
+
if (seenTables.has(table2.table)) {
|
|
58239
|
+
throw new TypeError(`${tablePath}.table duplicates ${table2.table}.`);
|
|
58240
|
+
}
|
|
58241
|
+
seenTables.add(table2.table);
|
|
58242
|
+
const spec = known.get(table2.table);
|
|
58243
|
+
if (!Array.isArray(table2.rows)) {
|
|
58244
|
+
throw new TypeError(`${tablePath}.rows must be an array.`);
|
|
58245
|
+
}
|
|
58246
|
+
if (table2.rows.length > BUY_CAPTURE_MAX_CHILD_ROWS) {
|
|
58247
|
+
throw new TypeError(
|
|
58248
|
+
`${tablePath}.rows exceeds the ${BUY_CAPTURE_MAX_CHILD_ROWS}-row bound.`
|
|
58249
|
+
);
|
|
58250
|
+
}
|
|
58251
|
+
const seenRows = /* @__PURE__ */ new Set();
|
|
58252
|
+
const rows = table2.rows.map((row, rowIndex) => {
|
|
58253
|
+
const rowPath = `${tablePath}.rows[${rowIndex}]`;
|
|
58254
|
+
const parsed = parseClosedWireRow(row, spec.fields, rowPath);
|
|
58255
|
+
if (seenRows.has(parsed.name)) {
|
|
58256
|
+
throw new TypeError(`${rowPath} duplicates child row ${parsed.name}.`);
|
|
58257
|
+
}
|
|
58258
|
+
seenRows.add(parsed.name);
|
|
58259
|
+
return parsed;
|
|
58260
|
+
});
|
|
58261
|
+
return { table: spec.table, rows };
|
|
58262
|
+
});
|
|
58263
|
+
}
|
|
58264
|
+
function parseClosedWireRow(value, specs, path) {
|
|
58265
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
58266
|
+
throw new TypeError(`${path} must be an object.`);
|
|
58267
|
+
}
|
|
58268
|
+
const root = value;
|
|
58269
|
+
const keys = Object.keys(root).toSorted();
|
|
58270
|
+
const allowed = root.idx === void 0 ? ["fields", "name"] : ["fields", "idx", "name"];
|
|
58271
|
+
if (keys.length !== allowed.length || keys.some((key, index) => key !== allowed[index])) {
|
|
58272
|
+
throw new TypeError(`${path} contains missing or unsupported fields.`);
|
|
58273
|
+
}
|
|
58274
|
+
const name = requiredWireName(root.name, `${path}.name`);
|
|
58275
|
+
return {
|
|
58276
|
+
name,
|
|
58277
|
+
...root.idx === void 0 ? {} : {
|
|
58278
|
+
idx: requiredWireIdx(root.idx, `${path}.idx`)
|
|
58279
|
+
},
|
|
58280
|
+
fields: parseClosedWireFields(root.fields, specs, `${path}.fields`)
|
|
58281
|
+
};
|
|
58282
|
+
}
|
|
58283
|
+
function parseWireValue(value, spec, path) {
|
|
58284
|
+
switch (spec.kind) {
|
|
58285
|
+
case "string":
|
|
58286
|
+
if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
|
|
58287
|
+
throw new TypeError(`${path} must be a non-empty unpadded string.`);
|
|
58288
|
+
}
|
|
58289
|
+
return value;
|
|
58290
|
+
case "decimal":
|
|
58291
|
+
if (typeof value !== "string" || !DECIMAL.test(value)) {
|
|
58292
|
+
throw new TypeError(`${path} must be a canonical decimal string.`);
|
|
58293
|
+
}
|
|
58294
|
+
return value;
|
|
58295
|
+
case "boolean":
|
|
58296
|
+
if (value !== true && value !== false) {
|
|
58297
|
+
throw new TypeError(`${path} must be a boolean.`);
|
|
58298
|
+
}
|
|
58299
|
+
return value;
|
|
58300
|
+
case "currency":
|
|
58301
|
+
if (typeof value !== "string" || !CURRENCY.test(value)) {
|
|
58302
|
+
throw new TypeError(`${path} must be a 3-letter currency code.`);
|
|
58303
|
+
}
|
|
58304
|
+
return value;
|
|
58305
|
+
case "date":
|
|
58306
|
+
return calendarDate(value, path);
|
|
58307
|
+
case "integer":
|
|
58308
|
+
if (!Number.isSafeInteger(value)) {
|
|
58309
|
+
throw new TypeError(`${path} must be a safe integer.`);
|
|
58310
|
+
}
|
|
58311
|
+
return value;
|
|
58312
|
+
}
|
|
58313
|
+
}
|
|
58314
|
+
function requiredWireName(value, path) {
|
|
58315
|
+
if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
|
|
58316
|
+
throw new TypeError(`${path} must be a non-empty unpadded string.`);
|
|
58317
|
+
}
|
|
58318
|
+
if (value.length > BUY_CAPTURE_MAX_NAME_LENGTH) {
|
|
58319
|
+
throw new TypeError(
|
|
58320
|
+
`${path} exceeds the ${BUY_CAPTURE_MAX_NAME_LENGTH}-character bound.`
|
|
58321
|
+
);
|
|
58322
|
+
}
|
|
58323
|
+
return value;
|
|
58324
|
+
}
|
|
58325
|
+
function requiredWireIdx(value, path) {
|
|
58326
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
58327
|
+
throw new TypeError(`${path} must be a non-negative integer.`);
|
|
58328
|
+
}
|
|
58329
|
+
return value;
|
|
58330
|
+
}
|
|
58331
|
+
|
|
58332
|
+
// src/buy/site.ts
|
|
58333
|
+
async function sourceInstanceFromClient(client) {
|
|
58334
|
+
const normalized = client.normalizedSiteUrl();
|
|
58335
|
+
return {
|
|
58336
|
+
kind: BUY_SOURCE_INSTANCE_KIND,
|
|
58337
|
+
siteId: await sha256FingerprintOfUtf8(normalized)
|
|
58338
|
+
};
|
|
58339
|
+
}
|
|
58340
|
+
|
|
58341
|
+
// src/buy/capture.ts
|
|
58342
|
+
var CALLER_AUTHORITY_KEYS = [
|
|
58343
|
+
"sourceInstance",
|
|
58344
|
+
"capturedAt",
|
|
58345
|
+
"fingerprint",
|
|
58346
|
+
"canonicalText",
|
|
58347
|
+
"byteCount",
|
|
58348
|
+
"endpoint",
|
|
58349
|
+
"uri",
|
|
58350
|
+
"credentials",
|
|
58351
|
+
"baseUrl",
|
|
58352
|
+
"siteId",
|
|
58353
|
+
"attestedTimestamp",
|
|
58354
|
+
"digest"
|
|
58355
|
+
];
|
|
58356
|
+
function parseBuyCaptureInput(value) {
|
|
58357
|
+
const root = asRecord(value, "buy capture input");
|
|
58358
|
+
for (const key of CALLER_AUTHORITY_KEYS) {
|
|
58359
|
+
if (key in root) {
|
|
58360
|
+
throw new BuyCaptureError(
|
|
58361
|
+
"BUY_CAPTURE_CALLER_AUTHORITY_REJECTED",
|
|
58362
|
+
`buy capture input must not include caller-supplied ${key}.`,
|
|
58363
|
+
{
|
|
58364
|
+
recovery: "Omit sourceInstance, capturedAt, fingerprints, endpoints, and credentials; the configured FrappeClient and server clock own those fields.",
|
|
58365
|
+
context: { key }
|
|
58366
|
+
}
|
|
58367
|
+
);
|
|
58368
|
+
}
|
|
58369
|
+
}
|
|
58370
|
+
const keys = Object.keys(root);
|
|
58371
|
+
if (keys.length !== 1 || keys[0] !== "documents") {
|
|
58372
|
+
throw new BuyCaptureError(
|
|
58373
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58374
|
+
"buy capture input must contain only documents.",
|
|
58375
|
+
{
|
|
58376
|
+
recovery: "Pass a bounded list of exact {doctype, name, expectedModified?} references.",
|
|
58377
|
+
context: { keys }
|
|
58378
|
+
}
|
|
58379
|
+
);
|
|
58380
|
+
}
|
|
58381
|
+
if (!Array.isArray(root.documents)) {
|
|
58382
|
+
throw new BuyCaptureError(
|
|
58383
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58384
|
+
"buy capture input.documents must be an array.",
|
|
58385
|
+
{
|
|
58386
|
+
recovery: "Pass a dense list of exact document references."
|
|
58387
|
+
}
|
|
58388
|
+
);
|
|
58389
|
+
}
|
|
58390
|
+
if (root.documents.length === 0) {
|
|
58391
|
+
throw new BuyCaptureError(
|
|
58392
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58393
|
+
"buy capture input.documents must not be empty.",
|
|
58394
|
+
{
|
|
58395
|
+
recovery: "Name the exact documents to capture."
|
|
58396
|
+
}
|
|
58397
|
+
);
|
|
58398
|
+
}
|
|
58399
|
+
if (root.documents.length > BUY_CAPTURE_MAX_DOCUMENTS) {
|
|
58400
|
+
throw new BuyCaptureError(
|
|
58401
|
+
"BUY_CAPTURE_BOUNDS_EXCEEDED",
|
|
58402
|
+
`buy capture input.documents exceeds the ${BUY_CAPTURE_MAX_DOCUMENTS}-document bound.`,
|
|
58403
|
+
{
|
|
58404
|
+
recovery: "Narrow the capture; silent pagination is not allowed.",
|
|
58405
|
+
context: {
|
|
58406
|
+
limit: BUY_CAPTURE_MAX_DOCUMENTS,
|
|
58407
|
+
actual: root.documents.length
|
|
58408
|
+
}
|
|
58409
|
+
}
|
|
58410
|
+
);
|
|
58411
|
+
}
|
|
58412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
58413
|
+
const documents = root.documents.map(
|
|
58414
|
+
(item, index) => parseDocumentRef(item, index, seen)
|
|
58415
|
+
);
|
|
58416
|
+
return { documents };
|
|
58417
|
+
}
|
|
58418
|
+
async function parseBuySourceCapture(value) {
|
|
58419
|
+
const root = exactRecord(value, [
|
|
58420
|
+
"schemaVersion",
|
|
58421
|
+
"sourceInstance",
|
|
58422
|
+
"capturedAt",
|
|
58423
|
+
"documents",
|
|
58424
|
+
"consistency"
|
|
58425
|
+
], "buy source capture");
|
|
58426
|
+
literal2(
|
|
58427
|
+
root.schemaVersion,
|
|
58428
|
+
BUY_SOURCE_CAPTURE_SCHEMA,
|
|
58429
|
+
"buy source capture.schemaVersion"
|
|
58430
|
+
);
|
|
58431
|
+
const source = exactRecord(
|
|
58432
|
+
root.sourceInstance,
|
|
58433
|
+
["kind", "siteId"],
|
|
58434
|
+
"buy source capture.sourceInstance"
|
|
58435
|
+
);
|
|
58436
|
+
literal2(
|
|
58437
|
+
source.kind,
|
|
58438
|
+
BUY_SOURCE_INSTANCE_KIND,
|
|
58439
|
+
"buy source capture.sourceInstance.kind"
|
|
58440
|
+
);
|
|
58441
|
+
const documents = [];
|
|
58442
|
+
const rawDocuments = boundedArray(
|
|
58443
|
+
root.documents,
|
|
58444
|
+
BUY_CAPTURE_MAX_DOCUMENTS,
|
|
58445
|
+
"buy source capture.documents"
|
|
58446
|
+
);
|
|
58447
|
+
if (rawDocuments.length === 0) {
|
|
58448
|
+
throw new TypeError("buy source capture.documents must not be empty.");
|
|
58449
|
+
}
|
|
58450
|
+
const seenDocuments = /* @__PURE__ */ new Set();
|
|
58451
|
+
for (let index = 0; index < rawDocuments.length; index += 1) {
|
|
58452
|
+
const parsed = await parseCapturedDocument(rawDocuments[index], index);
|
|
58453
|
+
const id = `${parsed.doctype}:${parsed.name}`;
|
|
58454
|
+
if (seenDocuments.has(id)) {
|
|
58455
|
+
throw new TypeError(
|
|
58456
|
+
`buy source capture.documents[${index}] duplicates ${id}.`
|
|
58457
|
+
);
|
|
58458
|
+
}
|
|
58459
|
+
seenDocuments.add(id);
|
|
58460
|
+
documents.push(parsed);
|
|
58461
|
+
}
|
|
58462
|
+
const consistency = exactRecord(
|
|
58463
|
+
root.consistency,
|
|
58464
|
+
["kind", "reads", "consistent"],
|
|
58465
|
+
"buy source capture.consistency"
|
|
58466
|
+
);
|
|
58467
|
+
literal2(
|
|
58468
|
+
consistency.kind,
|
|
58469
|
+
BUY_CONSISTENCY_KIND,
|
|
58470
|
+
"buy source capture.consistency.kind"
|
|
58471
|
+
);
|
|
58472
|
+
if (consistency.reads !== BUY_CONSISTENCY_READS) {
|
|
58473
|
+
throw new TypeError(
|
|
58474
|
+
"buy source capture.consistency.reads must be 2."
|
|
58475
|
+
);
|
|
58476
|
+
}
|
|
58477
|
+
if (consistency.consistent !== true) {
|
|
58478
|
+
throw new TypeError(
|
|
58479
|
+
"buy source capture.consistency.consistent must be true; inconsistent captures are not a usable payload."
|
|
58480
|
+
);
|
|
58481
|
+
}
|
|
58482
|
+
return {
|
|
58483
|
+
schemaVersion: BUY_SOURCE_CAPTURE_SCHEMA,
|
|
58484
|
+
sourceInstance: {
|
|
58485
|
+
kind: BUY_SOURCE_INSTANCE_KIND,
|
|
58486
|
+
siteId: fingerprint(
|
|
58487
|
+
source.siteId,
|
|
58488
|
+
"buy source capture.sourceInstance.siteId"
|
|
58489
|
+
)
|
|
58490
|
+
},
|
|
58491
|
+
capturedAt: canonicalTimestamp(
|
|
58492
|
+
root.capturedAt,
|
|
58493
|
+
"buy source capture.capturedAt"
|
|
58494
|
+
),
|
|
58495
|
+
documents,
|
|
58496
|
+
consistency: {
|
|
58497
|
+
kind: BUY_CONSISTENCY_KIND,
|
|
58498
|
+
reads: BUY_CONSISTENCY_READS,
|
|
58499
|
+
consistent: true
|
|
58500
|
+
}
|
|
58501
|
+
};
|
|
58502
|
+
}
|
|
58503
|
+
async function sealBuySourceCapture(capture) {
|
|
58504
|
+
const parsed = await parseBuySourceCapture(capture);
|
|
58505
|
+
const canonicalText = canonicalJson2(parsed, "buy source capture");
|
|
58506
|
+
const byteCount = utf8ByteCount(canonicalText);
|
|
58507
|
+
if (byteCount > BUY_CAPTURE_MAX_CANONICAL_BYTES) {
|
|
58508
|
+
throw new BuyCaptureError(
|
|
58509
|
+
"BUY_CAPTURE_BOUNDS_EXCEEDED",
|
|
58510
|
+
`buy source capture exceeds the ${BUY_CAPTURE_MAX_CANONICAL_BYTES}-byte bound.`,
|
|
58511
|
+
{
|
|
58512
|
+
recovery: "Narrow the capture; silent pagination is not allowed.",
|
|
58513
|
+
context: { limit: BUY_CAPTURE_MAX_CANONICAL_BYTES, actual: byteCount }
|
|
58514
|
+
}
|
|
58515
|
+
);
|
|
58516
|
+
}
|
|
58517
|
+
return {
|
|
58518
|
+
schemaVersion: BUY_SOURCE_CAPTURE_SCHEMA,
|
|
58519
|
+
capture: parsed,
|
|
58520
|
+
canonicalText,
|
|
58521
|
+
fingerprint: await sha256FingerprintOfUtf8(canonicalText),
|
|
58522
|
+
byteCount
|
|
58523
|
+
};
|
|
58524
|
+
}
|
|
58525
|
+
async function runBuyCapture(input, deps) {
|
|
58526
|
+
const parsedInput = parseBuyCaptureInput(input);
|
|
58527
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
58528
|
+
const capturedAt = canonicalTimestamp(
|
|
58529
|
+
now().toISOString(),
|
|
58530
|
+
"buy source capture.capturedAt"
|
|
58531
|
+
);
|
|
58532
|
+
const sourceInstance = await sourceInstanceFromClient(deps.client);
|
|
58533
|
+
const first = await readAll(parsedInput.documents, deps.client);
|
|
58534
|
+
const documents = await Promise.all(
|
|
58535
|
+
first.map((row) => projectBuyDocument(row.doctype, row.doc))
|
|
58536
|
+
);
|
|
58537
|
+
for (let index = 0; index < parsedInput.documents.length; index += 1) {
|
|
58538
|
+
const expected = parsedInput.documents[index].expectedModified;
|
|
58539
|
+
if (expected !== void 0 && documents[index].modified !== expected) {
|
|
58540
|
+
throw new BuyCaptureError(
|
|
58541
|
+
"BUY_CAPTURE_EXPECTED_MODIFIED_MISMATCH",
|
|
58542
|
+
`Document ${documents[index].doctype}:${documents[index].name} modified does not match expectedModified.`,
|
|
58543
|
+
{
|
|
58544
|
+
recovery: "Re-resolve the exact document revision before capturing.",
|
|
58545
|
+
context: {
|
|
58546
|
+
doctype: documents[index].doctype,
|
|
58547
|
+
name: documents[index].name,
|
|
58548
|
+
modified: documents[index].modified,
|
|
58549
|
+
expectedModified: expected
|
|
58550
|
+
}
|
|
58551
|
+
}
|
|
58552
|
+
);
|
|
58553
|
+
}
|
|
58554
|
+
}
|
|
58555
|
+
const second = await readAll(parsedInput.documents, deps.client);
|
|
58556
|
+
const reread = await Promise.all(
|
|
58557
|
+
second.map((row) => projectBuyDocument(row.doctype, row.doc))
|
|
58558
|
+
);
|
|
58559
|
+
for (let index = 0; index < documents.length; index += 1) {
|
|
58560
|
+
const a = documents[index];
|
|
58561
|
+
const b = reread[index];
|
|
58562
|
+
if (a.modified !== b.modified || a.fingerprint !== b.fingerprint) {
|
|
58563
|
+
throw new BuyCaptureError(
|
|
58564
|
+
"BUY_CAPTURE_INCONSISTENT",
|
|
58565
|
+
`Document ${a.doctype}:${a.name} changed between the two skipCache reads.`,
|
|
58566
|
+
{
|
|
58567
|
+
retryable: true,
|
|
58568
|
+
recovery: "Retry the capture. Repeated-read consistency failed; this is not a transactional snapshot.",
|
|
58569
|
+
context: {
|
|
58570
|
+
doctype: a.doctype,
|
|
58571
|
+
name: a.name,
|
|
58572
|
+
firstModified: a.modified,
|
|
58573
|
+
secondModified: b.modified,
|
|
58574
|
+
firstFingerprint: a.fingerprint,
|
|
58575
|
+
secondFingerprint: b.fingerprint
|
|
58576
|
+
}
|
|
58577
|
+
}
|
|
58578
|
+
);
|
|
58579
|
+
}
|
|
58580
|
+
}
|
|
58581
|
+
return await sealBuySourceCapture({
|
|
58582
|
+
schemaVersion: BUY_SOURCE_CAPTURE_SCHEMA,
|
|
58583
|
+
sourceInstance,
|
|
58584
|
+
capturedAt,
|
|
58585
|
+
documents,
|
|
58586
|
+
consistency: {
|
|
58587
|
+
kind: BUY_CONSISTENCY_KIND,
|
|
58588
|
+
reads: BUY_CONSISTENCY_READS,
|
|
58589
|
+
consistent: true
|
|
58590
|
+
}
|
|
58591
|
+
});
|
|
58592
|
+
}
|
|
58593
|
+
async function readAll(refs, client) {
|
|
58594
|
+
const docs = [];
|
|
58595
|
+
for (const ref of refs) {
|
|
58596
|
+
let doc;
|
|
58597
|
+
try {
|
|
58598
|
+
doc = await client.get(ref.doctype, ref.name, { skipCache: true });
|
|
58599
|
+
} catch (error2) {
|
|
58600
|
+
if (error2 instanceof FrappeAPIError && error2.status === 404) {
|
|
58601
|
+
throw new BuyCaptureError(
|
|
58602
|
+
"BUY_CAPTURE_MISSING_SOURCE",
|
|
58603
|
+
`Document ${ref.doctype}:${ref.name} was not found.`,
|
|
58604
|
+
{
|
|
58605
|
+
recovery: "Capture only documents that currently exist on the configured site.",
|
|
58606
|
+
context: { doctype: ref.doctype, name: ref.name }
|
|
58607
|
+
}
|
|
58608
|
+
);
|
|
58609
|
+
}
|
|
58610
|
+
throw error2;
|
|
58611
|
+
}
|
|
58612
|
+
assertExactReturnedIdentity(ref, doc);
|
|
58613
|
+
docs.push({ doctype: ref.doctype, doc });
|
|
58614
|
+
}
|
|
58615
|
+
return docs;
|
|
58616
|
+
}
|
|
58617
|
+
function assertExactReturnedIdentity(ref, doc) {
|
|
58618
|
+
if (doc.name !== ref.name) {
|
|
58619
|
+
throw new BuyCaptureError(
|
|
58620
|
+
"BUY_CAPTURE_IDENTITY_MISMATCH",
|
|
58621
|
+
`Requested ${ref.doctype}:${ref.name} but the client returned name ${String(doc.name)}.`,
|
|
58622
|
+
{
|
|
58623
|
+
recovery: "Refuse the response; do not relabel a different ERP document as the requested identity.",
|
|
58624
|
+
context: {
|
|
58625
|
+
requestedDoctype: ref.doctype,
|
|
58626
|
+
requestedName: ref.name,
|
|
58627
|
+
returnedName: doc.name
|
|
58628
|
+
}
|
|
58629
|
+
}
|
|
58630
|
+
);
|
|
58631
|
+
}
|
|
58632
|
+
if (doc.doctype !== void 0 && doc.doctype !== null && doc.doctype !== "" && doc.doctype !== ref.doctype) {
|
|
58633
|
+
throw new BuyCaptureError(
|
|
58634
|
+
"BUY_CAPTURE_IDENTITY_MISMATCH",
|
|
58635
|
+
`Requested ${ref.doctype}:${ref.name} but the client returned doctype ${String(doc.doctype)}.`,
|
|
58636
|
+
{
|
|
58637
|
+
recovery: "Refuse the response; do not relabel a different ERP document as the requested identity.",
|
|
58638
|
+
context: {
|
|
58639
|
+
requestedDoctype: ref.doctype,
|
|
58640
|
+
requestedName: ref.name,
|
|
58641
|
+
returnedDoctype: doc.doctype
|
|
58642
|
+
}
|
|
58643
|
+
}
|
|
58644
|
+
);
|
|
58645
|
+
}
|
|
58646
|
+
}
|
|
58647
|
+
function parseDocumentRef(value, index, seen) {
|
|
58648
|
+
const path = `buy capture input.documents[${index}]`;
|
|
58649
|
+
const root = asRecord(value, path);
|
|
58650
|
+
const keys = Object.keys(root).toSorted();
|
|
58651
|
+
const allowed = root.expectedModified === void 0 ? ["doctype", "name"] : ["doctype", "expectedModified", "name"];
|
|
58652
|
+
if (keys.length !== allowed.length || keys.some((key, keyIndex) => key !== allowed[keyIndex])) {
|
|
58653
|
+
throw new BuyCaptureError(
|
|
58654
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58655
|
+
`${path} must contain only doctype, name, and optional expectedModified.`,
|
|
58656
|
+
{
|
|
58657
|
+
recovery: "Do not pass endpoints, sourceInstance, or extra ERP fields.",
|
|
58658
|
+
context: { keys }
|
|
58659
|
+
}
|
|
58660
|
+
);
|
|
58661
|
+
}
|
|
58662
|
+
if (typeof root.doctype !== "string" || !isBuyCaptureDoctype(root.doctype)) {
|
|
58663
|
+
throw new BuyCaptureError(
|
|
58664
|
+
"BUY_CAPTURE_UNSUPPORTED_DOCTYPE",
|
|
58665
|
+
`${path}.doctype is not a closed Buy capture DocType.`,
|
|
58666
|
+
{
|
|
58667
|
+
recovery: `Use one of ${BUY_CAPTURE_DOCTYPE.join(", ")}. Generic doctypes cannot masquerade as a price.`,
|
|
58668
|
+
context: { doctype: root.doctype }
|
|
58669
|
+
}
|
|
58670
|
+
);
|
|
58671
|
+
}
|
|
58672
|
+
if (typeof root.name !== "string" || root.name.trim() === "" || root.name.trim() !== root.name) {
|
|
58673
|
+
throw new BuyCaptureError(
|
|
58674
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58675
|
+
`${path}.name must be the exact ERP document name.`,
|
|
58676
|
+
{ recovery: "Pass the exact document name, not a label." }
|
|
58677
|
+
);
|
|
58678
|
+
}
|
|
58679
|
+
if (root.name.length > BUY_CAPTURE_MAX_NAME_LENGTH) {
|
|
58680
|
+
throw new BuyCaptureError(
|
|
58681
|
+
"BUY_CAPTURE_BOUNDS_EXCEEDED",
|
|
58682
|
+
`${path}.name exceeds the ${BUY_CAPTURE_MAX_NAME_LENGTH}-character bound.`,
|
|
58683
|
+
{
|
|
58684
|
+
recovery: "Use the exact ERP document name.",
|
|
58685
|
+
context: {
|
|
58686
|
+
limit: BUY_CAPTURE_MAX_NAME_LENGTH,
|
|
58687
|
+
actual: root.name.length
|
|
58688
|
+
}
|
|
58689
|
+
}
|
|
58690
|
+
);
|
|
58691
|
+
}
|
|
58692
|
+
const id = `${root.doctype}:${root.name}`;
|
|
58693
|
+
if (seen.has(id)) {
|
|
58694
|
+
throw new BuyCaptureError(
|
|
58695
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58696
|
+
`${path} duplicates ${id}.`,
|
|
58697
|
+
{ recovery: "Each exact document reference may appear once." }
|
|
58698
|
+
);
|
|
58699
|
+
}
|
|
58700
|
+
seen.add(id);
|
|
58701
|
+
const expectedModified = root.expectedModified === void 0 ? void 0 : parseExpectedModified(root.expectedModified, `${path}.expectedModified`);
|
|
58702
|
+
return {
|
|
58703
|
+
doctype: root.doctype,
|
|
58704
|
+
name: root.name,
|
|
58705
|
+
...expectedModified !== void 0 ? { expectedModified } : {}
|
|
58706
|
+
};
|
|
58707
|
+
}
|
|
58708
|
+
function parseExpectedModified(value, path) {
|
|
58709
|
+
if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
|
|
58710
|
+
throw new BuyCaptureError(
|
|
58711
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58712
|
+
`${path} must be the exact Frappe modified timestamp.`,
|
|
58713
|
+
{ recovery: "Pass the document's modified value, not a caller digest." }
|
|
58714
|
+
);
|
|
58715
|
+
}
|
|
58716
|
+
try {
|
|
58717
|
+
frappeDatetime(value, path);
|
|
58718
|
+
} catch {
|
|
58719
|
+
throw new BuyCaptureError(
|
|
58720
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58721
|
+
`${path} is not a Frappe datetime.`,
|
|
58722
|
+
{ recovery: "Pass the document's modified value." }
|
|
58723
|
+
);
|
|
58724
|
+
}
|
|
58725
|
+
return value;
|
|
58726
|
+
}
|
|
58727
|
+
async function parseCapturedDocument(value, index) {
|
|
58728
|
+
const path = `buy source capture.documents[${index}]`;
|
|
58729
|
+
const root = record2(value, path);
|
|
58730
|
+
const keys = Object.keys(root).toSorted();
|
|
58731
|
+
const optional2 = /* @__PURE__ */ new Set(["docstatus", "status", "children"]);
|
|
58732
|
+
const required2 = [
|
|
58733
|
+
"doctype",
|
|
58734
|
+
"fields",
|
|
58735
|
+
"fingerprint",
|
|
58736
|
+
"modified",
|
|
58737
|
+
"name",
|
|
58738
|
+
"sourceCategory"
|
|
58739
|
+
];
|
|
58740
|
+
const actualRequired = keys.filter((key) => !optional2.has(key)).toSorted();
|
|
58741
|
+
if (actualRequired.length !== required2.length || actualRequired.some((key, keyIndex) => key !== required2[keyIndex])) {
|
|
58742
|
+
throw new TypeError(`${path} contains missing or unsupported fields.`);
|
|
58743
|
+
}
|
|
58744
|
+
const extra = keys.filter(
|
|
58745
|
+
(key) => !required2.includes(key) && !optional2.has(key)
|
|
58746
|
+
);
|
|
58747
|
+
if (extra.length > 0) {
|
|
58748
|
+
throw new TypeError(`${path} contains missing or unsupported fields.`);
|
|
58749
|
+
}
|
|
58750
|
+
const doctype = oneOf(
|
|
58751
|
+
root.doctype,
|
|
58752
|
+
BUY_CAPTURE_DOCTYPE,
|
|
58753
|
+
`${path}.doctype`
|
|
58754
|
+
);
|
|
58755
|
+
const document = {
|
|
58756
|
+
doctype,
|
|
58757
|
+
name: nonEmpty(root.name, `${path}.name`),
|
|
58758
|
+
modified: frappeDatetime(root.modified, `${path}.modified`),
|
|
58759
|
+
...root.docstatus === void 0 ? {} : {
|
|
58760
|
+
docstatus: parseDocstatus(root.docstatus, `${path}.docstatus`)
|
|
58761
|
+
},
|
|
58762
|
+
...root.status === void 0 ? {} : { status: nonEmpty(root.status, `${path}.status`) },
|
|
58763
|
+
sourceCategory: nonEmpty(
|
|
58764
|
+
root.sourceCategory,
|
|
58765
|
+
`${path}.sourceCategory`
|
|
58766
|
+
),
|
|
58767
|
+
fields: parseClosedWireFields(
|
|
58768
|
+
root.fields,
|
|
58769
|
+
BUY_DOCTYPE_FIELD_SPECS[doctype],
|
|
58770
|
+
`${path}.fields`
|
|
58771
|
+
),
|
|
58772
|
+
...root.children === void 0 ? {} : {
|
|
58773
|
+
children: parseClosedWireChildren(
|
|
58774
|
+
doctype,
|
|
58775
|
+
root.children,
|
|
58776
|
+
`${path}.children`
|
|
58777
|
+
)
|
|
58778
|
+
},
|
|
58779
|
+
fingerprint: fingerprint(root.fingerprint, `${path}.fingerprint`)
|
|
58780
|
+
};
|
|
58781
|
+
if (document.sourceCategory !== BUY_DOCTYPE_SOURCE_CATEGORY[doctype]) {
|
|
58782
|
+
throw new TypeError(
|
|
58783
|
+
`${path}.sourceCategory must match the closed DocType; a generic document cannot masquerade as a price.`
|
|
58784
|
+
);
|
|
58785
|
+
}
|
|
58786
|
+
const { fingerprint: _ignored, ...identity } = document;
|
|
58787
|
+
const expectedFingerprint = await sha256FingerprintOfUtf8(
|
|
58788
|
+
canonicalJson2(identity, `${path} identity`)
|
|
58789
|
+
);
|
|
58790
|
+
if (document.fingerprint !== expectedFingerprint) {
|
|
58791
|
+
throw new TypeError(
|
|
58792
|
+
`${path}.fingerprint must be SHA-256 of the document projection without fingerprint.`
|
|
58793
|
+
);
|
|
58794
|
+
}
|
|
58795
|
+
return document;
|
|
58796
|
+
}
|
|
58797
|
+
function parseDocstatus(value, path) {
|
|
58798
|
+
if (value !== 0 && value !== 1 && value !== 2) {
|
|
58799
|
+
throw new TypeError(`${path} must be 0, 1, or 2.`);
|
|
58800
|
+
}
|
|
58801
|
+
return value;
|
|
58802
|
+
}
|
|
58803
|
+
function asRecord(value, name) {
|
|
58804
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
58805
|
+
throw new BuyCaptureError(
|
|
58806
|
+
"BUY_CAPTURE_INVALID_INPUT",
|
|
58807
|
+
`${name} must be an object.`,
|
|
58808
|
+
{ recovery: "Pass a JSON object with exact keys." }
|
|
58809
|
+
);
|
|
58810
|
+
}
|
|
58811
|
+
return value;
|
|
58812
|
+
}
|
|
58813
|
+
|
|
58814
|
+
// src/tools/buy.ts
|
|
58815
|
+
var buyTools = [
|
|
58816
|
+
{
|
|
58817
|
+
name: "erpnext_buy_capture",
|
|
58818
|
+
annotations: { readOnlyHint: true },
|
|
58819
|
+
description: "Read-only bounded capture of exact ERPNext commercial documents (Item, BOM, Item Price, Supplier Quotation, Supplier, Price List, UOM, Currency Exchange). Two skipCache reads must agree on modified and the closed projection fingerprint. Returns canonical JSON + SHA-256; does not store CAS, create BOM/RFQ/PO, compute a cost total, or qualify a live ERP. Not a registered purchase capability.",
|
|
58820
|
+
category: "buy",
|
|
58821
|
+
inputSchema: {
|
|
58822
|
+
type: "object",
|
|
58823
|
+
additionalProperties: false,
|
|
58824
|
+
required: ["documents"],
|
|
58825
|
+
properties: {
|
|
58826
|
+
documents: {
|
|
58827
|
+
type: "array",
|
|
58828
|
+
minItems: 1,
|
|
58829
|
+
maxItems: BUY_CAPTURE_MAX_DOCUMENTS,
|
|
58830
|
+
description: "Exact document references. Do not pass sourceInstance, URL, credentials, capturedAt, or a caller digest.",
|
|
58831
|
+
items: {
|
|
58832
|
+
type: "object",
|
|
58833
|
+
additionalProperties: false,
|
|
58834
|
+
required: ["doctype", "name"],
|
|
58835
|
+
properties: {
|
|
58836
|
+
doctype: {
|
|
58837
|
+
type: "string",
|
|
58838
|
+
enum: [...BUY_CAPTURE_DOCTYPE],
|
|
58839
|
+
description: "Closed Buy capture DocType"
|
|
58840
|
+
},
|
|
58841
|
+
name: {
|
|
58842
|
+
type: "string",
|
|
58843
|
+
minLength: 1,
|
|
58844
|
+
maxLength: 140,
|
|
58845
|
+
description: "Exact ERP document name"
|
|
58846
|
+
},
|
|
58847
|
+
expectedModified: {
|
|
58848
|
+
type: "string",
|
|
58849
|
+
description: "Optional exact Frappe modified timestamp to match"
|
|
58850
|
+
}
|
|
58851
|
+
}
|
|
58852
|
+
}
|
|
58853
|
+
}
|
|
58854
|
+
}
|
|
58855
|
+
},
|
|
58856
|
+
handler: async (input, ctx) => {
|
|
58857
|
+
return await runBuyCapture(input, { client: ctx.client });
|
|
58858
|
+
}
|
|
58859
|
+
}
|
|
58860
|
+
];
|
|
58861
|
+
|
|
56893
58862
|
// src/tools/mod.ts
|
|
56894
58863
|
var toolsByCategory = {
|
|
56895
58864
|
sales: salesTools,
|
|
@@ -56905,7 +58874,8 @@ var toolsByCategory = {
|
|
|
56905
58874
|
operations: operationsTools,
|
|
56906
58875
|
setup: setupTools,
|
|
56907
58876
|
analytics: analyticsTools,
|
|
56908
|
-
kanban: kanbanTools
|
|
58877
|
+
kanban: kanbanTools,
|
|
58878
|
+
buy: buyTools
|
|
56909
58879
|
};
|
|
56910
58880
|
var allTools = [
|
|
56911
58881
|
...salesTools,
|
|
@@ -56921,7 +58891,8 @@ var allTools = [
|
|
|
56921
58891
|
...operationsTools,
|
|
56922
58892
|
...setupTools,
|
|
56923
58893
|
...analyticsTools,
|
|
56924
|
-
...kanbanTools
|
|
58894
|
+
...kanbanTools,
|
|
58895
|
+
...buyTools
|
|
56925
58896
|
];
|
|
56926
58897
|
function getToolsByCategory(category) {
|
|
56927
58898
|
return toolsByCategory[category] ?? [];
|
|
@@ -57071,46 +59042,211 @@ var DOCTYPE_GET_TOOLS = {
|
|
|
57071
59042
|
};
|
|
57072
59043
|
var DOCTYPE_SEND_MESSAGE_HINTS = {
|
|
57073
59044
|
"Customer": [
|
|
57074
|
-
{
|
|
57075
|
-
|
|
59045
|
+
{
|
|
59046
|
+
key: "orders",
|
|
59047
|
+
label: "Orders",
|
|
59048
|
+
message: "Show sales orders for customer {id}",
|
|
59049
|
+
tool: "erpnext_sales_order_list",
|
|
59050
|
+
args: { customer: "{id}", limit: 20 }
|
|
59051
|
+
},
|
|
59052
|
+
{
|
|
59053
|
+
key: "invoices",
|
|
59054
|
+
label: "Invoices",
|
|
59055
|
+
message: "Show sales invoices for customer {id}",
|
|
59056
|
+
tool: "erpnext_sales_invoice_list",
|
|
59057
|
+
args: { customer: "{id}", limit: 20 }
|
|
59058
|
+
}
|
|
57076
59059
|
],
|
|
57077
59060
|
"Sales Order": [
|
|
57078
|
-
{
|
|
57079
|
-
|
|
59061
|
+
{
|
|
59062
|
+
key: "invoices",
|
|
59063
|
+
label: "Invoices",
|
|
59064
|
+
message: "Show invoices linked to sales order {id}",
|
|
59065
|
+
tool: "erpnext_doc_list",
|
|
59066
|
+
args: {
|
|
59067
|
+
doctype: "Sales Invoice",
|
|
59068
|
+
fields: ["name", "customer", "posting_date", "status", "grand_total"],
|
|
59069
|
+
filters: [["Sales Invoice Item", "sales_order", "=", "{id}"]],
|
|
59070
|
+
limit: 20
|
|
59071
|
+
}
|
|
59072
|
+
},
|
|
59073
|
+
{
|
|
59074
|
+
key: "deliveries",
|
|
59075
|
+
label: "Delivery notes",
|
|
59076
|
+
message: "Show delivery notes for sales order {id}",
|
|
59077
|
+
tool: "erpnext_doc_list",
|
|
59078
|
+
args: {
|
|
59079
|
+
doctype: "Delivery Note",
|
|
59080
|
+
fields: ["name", "customer", "posting_date", "status"],
|
|
59081
|
+
filters: [["Delivery Note Item", "against_sales_order", "=", "{id}"]],
|
|
59082
|
+
limit: 20
|
|
59083
|
+
}
|
|
59084
|
+
}
|
|
57080
59085
|
],
|
|
57081
59086
|
"Sales Invoice": [
|
|
57082
|
-
{
|
|
59087
|
+
{
|
|
59088
|
+
key: "payments",
|
|
59089
|
+
label: "Payments",
|
|
59090
|
+
message: "Show payment entries for invoice {id}",
|
|
59091
|
+
tool: "erpnext_doc_list",
|
|
59092
|
+
args: {
|
|
59093
|
+
doctype: "Payment Entry",
|
|
59094
|
+
fields: [
|
|
59095
|
+
"name",
|
|
59096
|
+
"posting_date",
|
|
59097
|
+
"paid_amount",
|
|
59098
|
+
"mode_of_payment",
|
|
59099
|
+
"docstatus"
|
|
59100
|
+
],
|
|
59101
|
+
filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
|
|
59102
|
+
limit: 20
|
|
59103
|
+
}
|
|
59104
|
+
}
|
|
59105
|
+
],
|
|
59106
|
+
"Purchase Invoice": [
|
|
59107
|
+
{
|
|
59108
|
+
key: "payments",
|
|
59109
|
+
label: "Payments",
|
|
59110
|
+
message: "Show payment entries for purchase invoice {id}",
|
|
59111
|
+
tool: "erpnext_doc_list",
|
|
59112
|
+
args: {
|
|
59113
|
+
doctype: "Payment Entry",
|
|
59114
|
+
fields: [
|
|
59115
|
+
"name",
|
|
59116
|
+
"posting_date",
|
|
59117
|
+
"paid_amount",
|
|
59118
|
+
"mode_of_payment",
|
|
59119
|
+
"docstatus"
|
|
59120
|
+
],
|
|
59121
|
+
filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
|
|
59122
|
+
limit: 20
|
|
59123
|
+
},
|
|
59124
|
+
kind: "list"
|
|
59125
|
+
}
|
|
57083
59126
|
],
|
|
57084
59127
|
"Item": [
|
|
57085
|
-
{
|
|
57086
|
-
|
|
59128
|
+
{
|
|
59129
|
+
key: "stock",
|
|
59130
|
+
label: "Stock",
|
|
59131
|
+
message: "Show stock balance for item {id}",
|
|
59132
|
+
tool: "erpnext_stock_balance",
|
|
59133
|
+
args: { item_code: "{id}" }
|
|
59134
|
+
},
|
|
59135
|
+
{
|
|
59136
|
+
key: "orders",
|
|
59137
|
+
label: "Orders",
|
|
59138
|
+
message: "Show sales orders containing item {id}",
|
|
59139
|
+
tool: "erpnext_doc_list",
|
|
59140
|
+
args: {
|
|
59141
|
+
doctype: "Sales Order",
|
|
59142
|
+
fields: ["name", "customer", "transaction_date", "status"],
|
|
59143
|
+
filters: [["Sales Order Item", "item_code", "=", "{id}"]],
|
|
59144
|
+
limit: 20
|
|
59145
|
+
}
|
|
59146
|
+
}
|
|
57087
59147
|
],
|
|
57088
59148
|
"Supplier": [
|
|
57089
|
-
{
|
|
57090
|
-
|
|
59149
|
+
{
|
|
59150
|
+
key: "po",
|
|
59151
|
+
label: "PO",
|
|
59152
|
+
message: "Show purchase orders for supplier {id}",
|
|
59153
|
+
tool: "erpnext_purchase_order_list",
|
|
59154
|
+
args: { supplier: "{id}", limit: 20 }
|
|
59155
|
+
},
|
|
59156
|
+
{
|
|
59157
|
+
key: "invoices",
|
|
59158
|
+
label: "Invoices",
|
|
59159
|
+
message: "Show purchase invoices for supplier {id}",
|
|
59160
|
+
tool: "erpnext_purchase_invoice_list",
|
|
59161
|
+
args: { supplier: "{id}", limit: 20 }
|
|
59162
|
+
}
|
|
57091
59163
|
],
|
|
57092
59164
|
"Purchase Order": [
|
|
57093
|
-
{
|
|
57094
|
-
|
|
59165
|
+
{
|
|
59166
|
+
key: "receipt",
|
|
59167
|
+
label: "Receipt",
|
|
59168
|
+
message: "Show purchase receipts for order {id}",
|
|
59169
|
+
tool: "erpnext_doc_list",
|
|
59170
|
+
args: {
|
|
59171
|
+
doctype: "Purchase Receipt",
|
|
59172
|
+
fields: ["name", "supplier", "posting_date", "status"],
|
|
59173
|
+
filters: [["Purchase Receipt Item", "purchase_order", "=", "{id}"]],
|
|
59174
|
+
limit: 20
|
|
59175
|
+
}
|
|
59176
|
+
},
|
|
59177
|
+
{
|
|
59178
|
+
key: "invoice",
|
|
59179
|
+
label: "Invoice",
|
|
59180
|
+
message: "Show purchase invoices for order {id}",
|
|
59181
|
+
tool: "erpnext_doc_list",
|
|
59182
|
+
args: {
|
|
59183
|
+
doctype: "Purchase Invoice",
|
|
59184
|
+
fields: ["name", "supplier", "posting_date", "status", "grand_total"],
|
|
59185
|
+
filters: [["Purchase Invoice Item", "purchase_order", "=", "{id}"]],
|
|
59186
|
+
limit: 20
|
|
59187
|
+
}
|
|
59188
|
+
}
|
|
57095
59189
|
],
|
|
57096
59190
|
"Employee": [
|
|
57097
|
-
{
|
|
57098
|
-
|
|
59191
|
+
{
|
|
59192
|
+
key: "attendance",
|
|
59193
|
+
label: "Attendance",
|
|
59194
|
+
message: "Show attendance for employee {id}",
|
|
59195
|
+
tool: "erpnext_attendance_list",
|
|
59196
|
+
args: { employee: "{id}", limit: 20 }
|
|
59197
|
+
},
|
|
59198
|
+
{
|
|
59199
|
+
key: "leaves",
|
|
59200
|
+
label: "Leaves",
|
|
59201
|
+
message: "Show leave applications for employee {id}",
|
|
59202
|
+
tool: "erpnext_leave_application_list",
|
|
59203
|
+
args: { employee: "{id}", limit: 20 }
|
|
59204
|
+
}
|
|
57099
59205
|
],
|
|
57100
59206
|
"Project": [
|
|
57101
|
-
{
|
|
57102
|
-
|
|
59207
|
+
{
|
|
59208
|
+
key: "tasks",
|
|
59209
|
+
label: "Tasks",
|
|
59210
|
+
message: "Show tasks for project {id}",
|
|
59211
|
+
tool: "erpnext_task_list",
|
|
59212
|
+
args: { project: "{id}", limit: 20 }
|
|
59213
|
+
},
|
|
59214
|
+
{
|
|
59215
|
+
key: "timesheets",
|
|
59216
|
+
label: "Timesheets",
|
|
59217
|
+
message: "Show timesheets for project {id}",
|
|
59218
|
+
tool: "erpnext_timesheet_list",
|
|
59219
|
+
args: { project: "{id}", limit: 20 }
|
|
59220
|
+
}
|
|
57103
59221
|
],
|
|
57104
59222
|
"Task": [
|
|
57105
|
-
{
|
|
59223
|
+
{
|
|
59224
|
+
key: "timesheets",
|
|
59225
|
+
label: "Timesheets",
|
|
59226
|
+
message: "Show timesheets for task {id}",
|
|
59227
|
+
tool: "erpnext_doc_list",
|
|
59228
|
+
args: {
|
|
59229
|
+
doctype: "Timesheet",
|
|
59230
|
+
fields: ["name", "employee", "start_date", "total_hours", "status"],
|
|
59231
|
+
filters: [["Timesheet Detail", "task", "=", "{id}"]],
|
|
59232
|
+
limit: 20
|
|
59233
|
+
}
|
|
59234
|
+
}
|
|
57106
59235
|
],
|
|
57107
59236
|
"Lead": [
|
|
57108
|
-
{
|
|
59237
|
+
{
|
|
59238
|
+
key: "opportunities",
|
|
59239
|
+
label: "Opportunities",
|
|
59240
|
+
message: "Show opportunities for lead {id}",
|
|
59241
|
+
tool: "erpnext_opportunity_list",
|
|
59242
|
+
// Le handler exige opportunity_from avec party_name.
|
|
59243
|
+
args: { opportunity_from: "Lead", party_name: "{id}", limit: 20 }
|
|
59244
|
+
}
|
|
57109
59245
|
]
|
|
57110
59246
|
};
|
|
57111
59247
|
var KPI_DRILL_DOWN = {
|
|
57112
59248
|
"erpnext_kpi_revenue": {
|
|
57113
|
-
_drillDown: "Show all sales
|
|
59249
|
+
_drillDown: "Show all sales orders for this month",
|
|
57114
59250
|
_trendDrillDown: "Show revenue trend chart for the last 12 months"
|
|
57115
59251
|
},
|
|
57116
59252
|
"erpnext_kpi_outstanding": {
|
|
@@ -57122,7 +59258,7 @@ var KPI_DRILL_DOWN = {
|
|
|
57122
59258
|
_trendDrillDown: "Show order breakdown chart for the last 6 months"
|
|
57123
59259
|
},
|
|
57124
59260
|
"erpnext_kpi_gross_margin": {
|
|
57125
|
-
_drillDown: "Show
|
|
59261
|
+
_drillDown: "Show the non-cancelled Sales Order Items and Bin valuation rates used to estimate this gross margin",
|
|
57126
59262
|
_trendDrillDown: "Show profit and loss chart for the last 12 months"
|
|
57127
59263
|
},
|
|
57128
59264
|
"erpnext_kpi_overdue": {
|
|
@@ -57133,12 +59269,522 @@ var KPI_DRILL_DOWN = {
|
|
|
57133
59269
|
var CHART_DRILL_DOWN = {
|
|
57134
59270
|
"erpnext_sales_chart": "Show sales invoices for {label}",
|
|
57135
59271
|
"erpnext_stock_chart": "Show stock balance for item {label}",
|
|
57136
|
-
"erpnext_revenue_trend": "Show sales
|
|
59272
|
+
"erpnext_revenue_trend": "Show sales orders for month {label}",
|
|
57137
59273
|
"erpnext_order_breakdown": "Show sales orders for {label}",
|
|
57138
|
-
"erpnext_ar_aging": "Show
|
|
59274
|
+
"erpnext_ar_aging": "Show outstanding sales invoices for customer {label}",
|
|
57139
59275
|
"erpnext_gross_profit": "Show gross profit details for {label}",
|
|
57140
|
-
"erpnext_profit_loss": "Show
|
|
59276
|
+
"erpnext_profit_loss": "Show submitted sales and purchase orders for month {label}"
|
|
57141
59277
|
};
|
|
59278
|
+
var INVOICE_ITEM_HINTS = [
|
|
59279
|
+
{
|
|
59280
|
+
key: "item",
|
|
59281
|
+
label: "Item",
|
|
59282
|
+
message: "Show item {item}",
|
|
59283
|
+
tool: "erpnext_item_get",
|
|
59284
|
+
args: { name: "{item}" },
|
|
59285
|
+
kind: "record"
|
|
59286
|
+
},
|
|
59287
|
+
{
|
|
59288
|
+
key: "stock",
|
|
59289
|
+
label: "Stock",
|
|
59290
|
+
message: "Show stock balance for item {item}",
|
|
59291
|
+
tool: "erpnext_stock_balance",
|
|
59292
|
+
args: { item_code: "{item}", limit: 50 },
|
|
59293
|
+
kind: "list"
|
|
59294
|
+
}
|
|
59295
|
+
];
|
|
59296
|
+
function nonEmptyDocumentString(document, key) {
|
|
59297
|
+
const value = document?.[key];
|
|
59298
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
59299
|
+
}
|
|
59300
|
+
function documentPartyHint(doctype, document) {
|
|
59301
|
+
if (doctype === "Sales Order" || doctype === "Sales Invoice") {
|
|
59302
|
+
const customer = nonEmptyDocumentString(document, "customer");
|
|
59303
|
+
return customer ? {
|
|
59304
|
+
key: "customer",
|
|
59305
|
+
label: "Customer",
|
|
59306
|
+
message: `Show customer ${customer}`,
|
|
59307
|
+
tool: "erpnext_customer_get",
|
|
59308
|
+
args: { name: customer },
|
|
59309
|
+
kind: "record"
|
|
59310
|
+
} : null;
|
|
59311
|
+
}
|
|
59312
|
+
if (doctype === "Purchase Invoice") {
|
|
59313
|
+
const supplier = nonEmptyDocumentString(document, "supplier");
|
|
59314
|
+
return supplier ? {
|
|
59315
|
+
key: "supplier",
|
|
59316
|
+
label: "Supplier",
|
|
59317
|
+
message: `Show supplier ${supplier}`,
|
|
59318
|
+
tool: "erpnext_supplier_get",
|
|
59319
|
+
args: { name: supplier },
|
|
59320
|
+
kind: "record"
|
|
59321
|
+
} : null;
|
|
59322
|
+
}
|
|
59323
|
+
if (doctype !== "Quotation") return null;
|
|
59324
|
+
const party = nonEmptyDocumentString(document, "party_name");
|
|
59325
|
+
const target = nonEmptyDocumentString(document, "quotation_to");
|
|
59326
|
+
if (!party || target !== "Customer" && target !== "Lead") return null;
|
|
59327
|
+
return target === "Customer" ? {
|
|
59328
|
+
key: "customer",
|
|
59329
|
+
label: "Customer",
|
|
59330
|
+
message: `Show customer ${party}`,
|
|
59331
|
+
tool: "erpnext_customer_get",
|
|
59332
|
+
args: { name: party },
|
|
59333
|
+
kind: "record"
|
|
59334
|
+
} : {
|
|
59335
|
+
key: "lead",
|
|
59336
|
+
label: "Lead",
|
|
59337
|
+
message: `Show lead ${party}`,
|
|
59338
|
+
tool: "erpnext_lead_get",
|
|
59339
|
+
args: { name: party },
|
|
59340
|
+
kind: "record"
|
|
59341
|
+
};
|
|
59342
|
+
}
|
|
59343
|
+
function documentNavigationHints(doctype, document) {
|
|
59344
|
+
const base = DOCTYPE_SEND_MESSAGE_HINTS[doctype] ?? [];
|
|
59345
|
+
const party = documentPartyHint(doctype, document);
|
|
59346
|
+
if (!party) return [...base];
|
|
59347
|
+
return doctype === "Sales Order" ? [party, ...base] : [...base, party];
|
|
59348
|
+
}
|
|
59349
|
+
var INVOICE_LINE_ITEM_DOCTYPES = /* @__PURE__ */ new Set([
|
|
59350
|
+
"Sales Order",
|
|
59351
|
+
"Sales Invoice",
|
|
59352
|
+
"Purchase Invoice",
|
|
59353
|
+
"Quotation"
|
|
59354
|
+
]);
|
|
59355
|
+
function invoiceNavigationHints(doctype, document) {
|
|
59356
|
+
const documentHints = documentNavigationHints(doctype, document);
|
|
59357
|
+
return INVOICE_LINE_ITEM_DOCTYPES.has(doctype) ? [...documentHints, ...INVOICE_ITEM_HINTS] : documentHints;
|
|
59358
|
+
}
|
|
59359
|
+
var STOCK_HINTS = [
|
|
59360
|
+
{
|
|
59361
|
+
key: "item",
|
|
59362
|
+
label: "Item",
|
|
59363
|
+
message: "Show item {id}",
|
|
59364
|
+
tool: "erpnext_item_get",
|
|
59365
|
+
args: { name: "{id}" },
|
|
59366
|
+
kind: "record"
|
|
59367
|
+
},
|
|
59368
|
+
{
|
|
59369
|
+
key: "movements",
|
|
59370
|
+
label: "Stock entries",
|
|
59371
|
+
message: "Show stock entries for item {id}",
|
|
59372
|
+
tool: "erpnext_doc_list",
|
|
59373
|
+
args: {
|
|
59374
|
+
doctype: "Stock Entry",
|
|
59375
|
+
fields: ["name", "posting_date", "stock_entry_type", "docstatus"],
|
|
59376
|
+
filters: [["Stock Entry Detail", "item_code", "=", "{id}"]],
|
|
59377
|
+
limit: 20
|
|
59378
|
+
},
|
|
59379
|
+
kind: "list"
|
|
59380
|
+
},
|
|
59381
|
+
{
|
|
59382
|
+
key: "warehouse",
|
|
59383
|
+
label: "Warehouse stock",
|
|
59384
|
+
message: "Show stock chart for warehouse {warehouse}",
|
|
59385
|
+
tool: "erpnext_stock_chart",
|
|
59386
|
+
args: { warehouse: "{warehouse}", limit: 10 },
|
|
59387
|
+
kind: "chart"
|
|
59388
|
+
}
|
|
59389
|
+
];
|
|
59390
|
+
function monthRange(now) {
|
|
59391
|
+
const y = now.getFullYear();
|
|
59392
|
+
const m = now.getMonth();
|
|
59393
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
59394
|
+
const last = new Date(y, m + 1, 0).getDate();
|
|
59395
|
+
return {
|
|
59396
|
+
from: `${y}-${pad(m + 1)}-01`,
|
|
59397
|
+
to: `${y}-${pad(m + 1)}-${pad(last)}`,
|
|
59398
|
+
today: `${y}-${pad(m + 1)}-${pad(now.getDate())}`
|
|
59399
|
+
};
|
|
59400
|
+
}
|
|
59401
|
+
var INVOICE_LIST_FIELDS = [
|
|
59402
|
+
"name",
|
|
59403
|
+
"customer",
|
|
59404
|
+
"posting_date",
|
|
59405
|
+
"due_date",
|
|
59406
|
+
"status",
|
|
59407
|
+
"outstanding_amount"
|
|
59408
|
+
];
|
|
59409
|
+
var KPI_JUMPS = {
|
|
59410
|
+
"erpnext_kpi_revenue": (r) => ({
|
|
59411
|
+
number: {
|
|
59412
|
+
key: "sales_orders_month",
|
|
59413
|
+
label: "Sales orders this month",
|
|
59414
|
+
tool: "erpnext_doc_list",
|
|
59415
|
+
args: {
|
|
59416
|
+
doctype: "Sales Order",
|
|
59417
|
+
fields: [
|
|
59418
|
+
"name",
|
|
59419
|
+
"customer",
|
|
59420
|
+
"transaction_date",
|
|
59421
|
+
"status",
|
|
59422
|
+
"grand_total"
|
|
59423
|
+
],
|
|
59424
|
+
filters: [
|
|
59425
|
+
["transaction_date", ">=", r.from],
|
|
59426
|
+
["transaction_date", "<=", r.to],
|
|
59427
|
+
// comme le KPI : les commandes annulées ne comptent pas
|
|
59428
|
+
["docstatus", "<", 2]
|
|
59429
|
+
],
|
|
59430
|
+
limit: 20
|
|
59431
|
+
},
|
|
59432
|
+
kind: "list"
|
|
59433
|
+
},
|
|
59434
|
+
trend: {
|
|
59435
|
+
key: "revenue_trend",
|
|
59436
|
+
label: "Revenue trend",
|
|
59437
|
+
tool: "erpnext_revenue_trend",
|
|
59438
|
+
args: { months: 12 },
|
|
59439
|
+
kind: "chart"
|
|
59440
|
+
}
|
|
59441
|
+
}),
|
|
59442
|
+
"erpnext_kpi_outstanding": () => ({
|
|
59443
|
+
number: {
|
|
59444
|
+
key: "unpaid_invoices",
|
|
59445
|
+
label: "Unpaid invoices",
|
|
59446
|
+
tool: "erpnext_doc_list",
|
|
59447
|
+
args: {
|
|
59448
|
+
doctype: "Sales Invoice",
|
|
59449
|
+
fields: INVOICE_LIST_FIELDS,
|
|
59450
|
+
filters: [["outstanding_amount", ">", 0], ["docstatus", "=", 1]],
|
|
59451
|
+
limit: 20
|
|
59452
|
+
},
|
|
59453
|
+
kind: "list"
|
|
59454
|
+
},
|
|
59455
|
+
trend: {
|
|
59456
|
+
key: "receivables_aging",
|
|
59457
|
+
label: "Receivables aging",
|
|
59458
|
+
tool: "erpnext_ar_aging",
|
|
59459
|
+
args: {},
|
|
59460
|
+
kind: "chart"
|
|
59461
|
+
}
|
|
59462
|
+
}),
|
|
59463
|
+
"erpnext_kpi_orders": (r) => ({
|
|
59464
|
+
number: {
|
|
59465
|
+
key: "sales_orders_month",
|
|
59466
|
+
label: "Sales orders this month",
|
|
59467
|
+
tool: "erpnext_doc_list",
|
|
59468
|
+
args: {
|
|
59469
|
+
doctype: "Sales Order",
|
|
59470
|
+
fields: [
|
|
59471
|
+
"name",
|
|
59472
|
+
"customer",
|
|
59473
|
+
"transaction_date",
|
|
59474
|
+
"status",
|
|
59475
|
+
"grand_total"
|
|
59476
|
+
],
|
|
59477
|
+
filters: [
|
|
59478
|
+
["transaction_date", ">=", r.from],
|
|
59479
|
+
["transaction_date", "<=", r.to],
|
|
59480
|
+
// comme le KPI : les commandes annulées ne comptent pas
|
|
59481
|
+
["docstatus", "<", 2]
|
|
59482
|
+
],
|
|
59483
|
+
limit: 20
|
|
59484
|
+
},
|
|
59485
|
+
kind: "list"
|
|
59486
|
+
},
|
|
59487
|
+
trend: {
|
|
59488
|
+
key: "order_breakdown",
|
|
59489
|
+
label: "Order breakdown",
|
|
59490
|
+
tool: "erpnext_order_breakdown",
|
|
59491
|
+
args: {},
|
|
59492
|
+
kind: "chart"
|
|
59493
|
+
}
|
|
59494
|
+
}),
|
|
59495
|
+
"erpnext_kpi_gross_margin": () => ({
|
|
59496
|
+
trend: {
|
|
59497
|
+
key: "profit_loss",
|
|
59498
|
+
label: "Profit and loss",
|
|
59499
|
+
tool: "erpnext_profit_loss",
|
|
59500
|
+
args: { months: 12 },
|
|
59501
|
+
kind: "chart"
|
|
59502
|
+
}
|
|
59503
|
+
}),
|
|
59504
|
+
"erpnext_kpi_overdue": (r) => ({
|
|
59505
|
+
number: {
|
|
59506
|
+
key: "overdue_invoices",
|
|
59507
|
+
label: "Overdue invoices",
|
|
59508
|
+
tool: "erpnext_doc_list",
|
|
59509
|
+
args: {
|
|
59510
|
+
doctype: "Sales Invoice",
|
|
59511
|
+
fields: INVOICE_LIST_FIELDS,
|
|
59512
|
+
filters: [
|
|
59513
|
+
["due_date", "<", r.today],
|
|
59514
|
+
["outstanding_amount", ">", 0],
|
|
59515
|
+
["docstatus", "=", 1]
|
|
59516
|
+
],
|
|
59517
|
+
limit: 20
|
|
59518
|
+
},
|
|
59519
|
+
kind: "list"
|
|
59520
|
+
},
|
|
59521
|
+
trend: {
|
|
59522
|
+
key: "receivables_aging",
|
|
59523
|
+
label: "Receivables aging",
|
|
59524
|
+
tool: "erpnext_ar_aging",
|
|
59525
|
+
args: {},
|
|
59526
|
+
kind: "chart"
|
|
59527
|
+
}
|
|
59528
|
+
})
|
|
59529
|
+
};
|
|
59530
|
+
var FUNNEL_LIST_FIELDS = {
|
|
59531
|
+
"Leads": [
|
|
59532
|
+
"name",
|
|
59533
|
+
"lead_name",
|
|
59534
|
+
"company_name",
|
|
59535
|
+
"status",
|
|
59536
|
+
"lead_owner",
|
|
59537
|
+
"creation"
|
|
59538
|
+
],
|
|
59539
|
+
"Opportunities": [
|
|
59540
|
+
"name",
|
|
59541
|
+
"opportunity_from",
|
|
59542
|
+
"party_name",
|
|
59543
|
+
"status",
|
|
59544
|
+
"opportunity_amount",
|
|
59545
|
+
"currency",
|
|
59546
|
+
"probability",
|
|
59547
|
+
"opportunity_owner",
|
|
59548
|
+
"transaction_date"
|
|
59549
|
+
],
|
|
59550
|
+
"Quotations": [
|
|
59551
|
+
"name",
|
|
59552
|
+
"party_name",
|
|
59553
|
+
"transaction_date",
|
|
59554
|
+
"status",
|
|
59555
|
+
"grand_total"
|
|
59556
|
+
],
|
|
59557
|
+
"Orders": [
|
|
59558
|
+
"name",
|
|
59559
|
+
"customer",
|
|
59560
|
+
"transaction_date",
|
|
59561
|
+
"status",
|
|
59562
|
+
"grand_total"
|
|
59563
|
+
]
|
|
59564
|
+
};
|
|
59565
|
+
var FUNNEL_STAGE_JUMPS = {
|
|
59566
|
+
"Leads": {
|
|
59567
|
+
key: "leads",
|
|
59568
|
+
label: "Leads",
|
|
59569
|
+
tool: "erpnext_lead_list",
|
|
59570
|
+
args: { limit: 20 },
|
|
59571
|
+
kind: "list"
|
|
59572
|
+
},
|
|
59573
|
+
"Opportunities": {
|
|
59574
|
+
key: "opportunities",
|
|
59575
|
+
label: "Opportunities",
|
|
59576
|
+
tool: "erpnext_opportunity_list",
|
|
59577
|
+
args: { limit: 20 },
|
|
59578
|
+
kind: "list"
|
|
59579
|
+
},
|
|
59580
|
+
"Quotations": {
|
|
59581
|
+
key: "quotations",
|
|
59582
|
+
label: "Quotations",
|
|
59583
|
+
tool: "erpnext_doc_list",
|
|
59584
|
+
args: {
|
|
59585
|
+
doctype: "Quotation",
|
|
59586
|
+
fields: FUNNEL_LIST_FIELDS.Quotations,
|
|
59587
|
+
filters: [["docstatus", "!=", 2]],
|
|
59588
|
+
limit: 20
|
|
59589
|
+
},
|
|
59590
|
+
kind: "list"
|
|
59591
|
+
},
|
|
59592
|
+
"Orders": {
|
|
59593
|
+
key: "sales_orders",
|
|
59594
|
+
label: "Sales orders",
|
|
59595
|
+
tool: "erpnext_doc_list",
|
|
59596
|
+
args: {
|
|
59597
|
+
doctype: "Sales Order",
|
|
59598
|
+
fields: FUNNEL_LIST_FIELDS.Orders,
|
|
59599
|
+
filters: [["docstatus", "!=", 2]],
|
|
59600
|
+
limit: 20
|
|
59601
|
+
},
|
|
59602
|
+
kind: "list"
|
|
59603
|
+
}
|
|
59604
|
+
};
|
|
59605
|
+
function funnelPeriodStart(period, now) {
|
|
59606
|
+
const year = now.getFullYear();
|
|
59607
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
59608
|
+
if (period === "this_month") {
|
|
59609
|
+
return `${year}-${pad(now.getMonth() + 1)}-01`;
|
|
59610
|
+
}
|
|
59611
|
+
if (period === "this_quarter") {
|
|
59612
|
+
const firstMonth = Math.floor(now.getMonth() / 3) * 3 + 1;
|
|
59613
|
+
return `${year}-${pad(firstMonth)}-01`;
|
|
59614
|
+
}
|
|
59615
|
+
if (period === "this_year") return `${year}-01-01`;
|
|
59616
|
+
return void 0;
|
|
59617
|
+
}
|
|
59618
|
+
function funnelStageJumps(args, now) {
|
|
59619
|
+
const since = funnelPeriodStart(args.period, now);
|
|
59620
|
+
if (!since) return FUNNEL_STAGE_JUMPS;
|
|
59621
|
+
const scopedJump = (stage, doctype, dateField) => {
|
|
59622
|
+
const base = FUNNEL_STAGE_JUMPS[stage];
|
|
59623
|
+
const baseFilters = Array.isArray(base.args.filters) ? base.args.filters : [];
|
|
59624
|
+
return {
|
|
59625
|
+
...base,
|
|
59626
|
+
tool: "erpnext_doc_list",
|
|
59627
|
+
args: {
|
|
59628
|
+
doctype,
|
|
59629
|
+
fields: FUNNEL_LIST_FIELDS[stage],
|
|
59630
|
+
filters: [[dateField, ">=", since], ...baseFilters],
|
|
59631
|
+
limit: 20
|
|
59632
|
+
}
|
|
59633
|
+
};
|
|
59634
|
+
};
|
|
59635
|
+
return {
|
|
59636
|
+
"Leads": scopedJump("Leads", "Lead", "creation"),
|
|
59637
|
+
"Opportunities": scopedJump(
|
|
59638
|
+
"Opportunities",
|
|
59639
|
+
"Opportunity",
|
|
59640
|
+
"transaction_date"
|
|
59641
|
+
),
|
|
59642
|
+
"Quotations": scopedJump(
|
|
59643
|
+
"Quotations",
|
|
59644
|
+
"Quotation",
|
|
59645
|
+
"transaction_date"
|
|
59646
|
+
),
|
|
59647
|
+
"Orders": scopedJump("Orders", "Sales Order", "transaction_date")
|
|
59648
|
+
};
|
|
59649
|
+
}
|
|
59650
|
+
var SALES_ORDER_LIST_FIELDS = [
|
|
59651
|
+
"name",
|
|
59652
|
+
"customer",
|
|
59653
|
+
"transaction_date",
|
|
59654
|
+
"status",
|
|
59655
|
+
"grand_total"
|
|
59656
|
+
];
|
|
59657
|
+
var PURCHASE_ORDER_LIST_FIELDS = [
|
|
59658
|
+
"name",
|
|
59659
|
+
"supplier",
|
|
59660
|
+
"transaction_date",
|
|
59661
|
+
"status",
|
|
59662
|
+
"grand_total"
|
|
59663
|
+
];
|
|
59664
|
+
function monthLabel(d) {
|
|
59665
|
+
return `${d.toLocaleString("en", { month: "short" })} ${d.getFullYear().toString().slice(2)}`;
|
|
59666
|
+
}
|
|
59667
|
+
function monthBuckets(monthsBack, now) {
|
|
59668
|
+
const buckets = [];
|
|
59669
|
+
for (let m = 0; m < monthsBack; m++) {
|
|
59670
|
+
const d = new Date(
|
|
59671
|
+
now.getFullYear(),
|
|
59672
|
+
now.getMonth() - monthsBack + 1 + m,
|
|
59673
|
+
1
|
|
59674
|
+
);
|
|
59675
|
+
buckets.push({ label: monthLabel(d), range: monthRange(d) });
|
|
59676
|
+
}
|
|
59677
|
+
return buckets;
|
|
59678
|
+
}
|
|
59679
|
+
var docList = (label, doctype, fields, filters) => ({
|
|
59680
|
+
label,
|
|
59681
|
+
tool: "erpnext_doc_list",
|
|
59682
|
+
args: { doctype, fields, filters, limit: 20 },
|
|
59683
|
+
kind: "list"
|
|
59684
|
+
});
|
|
59685
|
+
function chartPointJumps(toolName, args, labels, now) {
|
|
59686
|
+
const jumps = {};
|
|
59687
|
+
const byLabel = (make) => {
|
|
59688
|
+
for (const label of labels) {
|
|
59689
|
+
if (label === "Unknown") continue;
|
|
59690
|
+
jumps[label] = make(label);
|
|
59691
|
+
}
|
|
59692
|
+
};
|
|
59693
|
+
switch (toolName) {
|
|
59694
|
+
case "erpnext_revenue_trend": {
|
|
59695
|
+
const months = Number(args.months ?? 6);
|
|
59696
|
+
for (const { label, range } of monthBuckets(months, now)) {
|
|
59697
|
+
if (!labels.includes(label)) continue;
|
|
59698
|
+
jumps[label] = docList(label, "Sales Order", SALES_ORDER_LIST_FIELDS, [
|
|
59699
|
+
["transaction_date", ">=", range.from],
|
|
59700
|
+
["transaction_date", "<=", range.to],
|
|
59701
|
+
["docstatus", "<", 2]
|
|
59702
|
+
]);
|
|
59703
|
+
}
|
|
59704
|
+
break;
|
|
59705
|
+
}
|
|
59706
|
+
case "erpnext_sales_chart": {
|
|
59707
|
+
const groupBy = String(args.group_by ?? "customer");
|
|
59708
|
+
byLabel((label) => {
|
|
59709
|
+
const filters = [
|
|
59710
|
+
groupBy === "item" ? ["Sales Invoice Item", "item_name", "=", label] : groupBy === "status" ? ["status", "=", label] : ["customer_name", "=", label]
|
|
59711
|
+
];
|
|
59712
|
+
if (groupBy === "status") {
|
|
59713
|
+
filters.push(["docstatus", "!=", 2]);
|
|
59714
|
+
} else if (groupBy === "item" || args.include_drafts !== true) {
|
|
59715
|
+
filters.push(["docstatus", "=", 1]);
|
|
59716
|
+
}
|
|
59717
|
+
return docList(label, "Sales Invoice", INVOICE_LIST_FIELDS, filters);
|
|
59718
|
+
});
|
|
59719
|
+
break;
|
|
59720
|
+
}
|
|
59721
|
+
case "erpnext_order_breakdown":
|
|
59722
|
+
case "erpnext_revenue_vs_orders":
|
|
59723
|
+
byLabel(
|
|
59724
|
+
(label) => docList(label, "Sales Order", SALES_ORDER_LIST_FIELDS, [
|
|
59725
|
+
["customer_name", "=", label],
|
|
59726
|
+
["docstatus", "<", 2]
|
|
59727
|
+
])
|
|
59728
|
+
);
|
|
59729
|
+
break;
|
|
59730
|
+
case "erpnext_ar_aging":
|
|
59731
|
+
case "erpnext_profit_loss":
|
|
59732
|
+
return void 0;
|
|
59733
|
+
case "erpnext_gross_profit": {
|
|
59734
|
+
const groupBy = String(args.group_by ?? "item");
|
|
59735
|
+
byLabel(
|
|
59736
|
+
(label) => docList(label, "Sales Invoice", INVOICE_LIST_FIELDS, [
|
|
59737
|
+
groupBy === "customer" ? ["customer_name", "=", label] : ["Sales Invoice Item", "item_name", "=", label],
|
|
59738
|
+
["docstatus", "=", 1]
|
|
59739
|
+
])
|
|
59740
|
+
);
|
|
59741
|
+
break;
|
|
59742
|
+
}
|
|
59743
|
+
case "erpnext_stock_chart":
|
|
59744
|
+
byLabel((label) => ({
|
|
59745
|
+
label,
|
|
59746
|
+
tool: "erpnext_stock_balance",
|
|
59747
|
+
args: {
|
|
59748
|
+
item_code: label,
|
|
59749
|
+
...typeof args.warehouse === "string" ? { warehouse: args.warehouse } : {},
|
|
59750
|
+
limit: 50
|
|
59751
|
+
},
|
|
59752
|
+
kind: "list"
|
|
59753
|
+
}));
|
|
59754
|
+
break;
|
|
59755
|
+
default:
|
|
59756
|
+
return void 0;
|
|
59757
|
+
}
|
|
59758
|
+
return Object.keys(jumps).length > 0 ? jumps : void 0;
|
|
59759
|
+
}
|
|
59760
|
+
function chartSeriesPointJumps(toolName, args, labels, now) {
|
|
59761
|
+
if (toolName !== "erpnext_profit_loss") return void 0;
|
|
59762
|
+
const jumps = {};
|
|
59763
|
+
const months = Number(args.months ?? 6);
|
|
59764
|
+
for (const { label, range } of monthBuckets(months, now)) {
|
|
59765
|
+
if (!labels.includes(label)) continue;
|
|
59766
|
+
const filters = [
|
|
59767
|
+
["transaction_date", ">=", range.from],
|
|
59768
|
+
["transaction_date", "<=", range.to],
|
|
59769
|
+
["docstatus", "=", 1]
|
|
59770
|
+
];
|
|
59771
|
+
jumps[label] = {
|
|
59772
|
+
"Income": docList(
|
|
59773
|
+
`${label} \xB7 Income`,
|
|
59774
|
+
"Sales Order",
|
|
59775
|
+
SALES_ORDER_LIST_FIELDS,
|
|
59776
|
+
filters
|
|
59777
|
+
),
|
|
59778
|
+
"Expenses": docList(
|
|
59779
|
+
`${label} \xB7 Expenses`,
|
|
59780
|
+
"Purchase Order",
|
|
59781
|
+
PURCHASE_ORDER_LIST_FIELDS,
|
|
59782
|
+
filters
|
|
59783
|
+
)
|
|
59784
|
+
};
|
|
59785
|
+
}
|
|
59786
|
+
return Object.keys(jumps).length > 0 ? jumps : void 0;
|
|
59787
|
+
}
|
|
57142
59788
|
function isChartViewer(result) {
|
|
57143
59789
|
const uri = result._meta?.ui?.resourceUri;
|
|
57144
59790
|
return uri === "ui://mcp-erpnext/chart-viewer";
|
|
@@ -57147,6 +59793,31 @@ function isKpiViewer(result) {
|
|
|
57147
59793
|
const uri = result._meta?.ui?.resourceUri;
|
|
57148
59794
|
return uri === "ui://mcp-erpnext/kpi-viewer";
|
|
57149
59795
|
}
|
|
59796
|
+
function isFunnelViewer(result) {
|
|
59797
|
+
const uri = result._meta?.ui?.resourceUri;
|
|
59798
|
+
return uri === "ui://mcp-erpnext/funnel-viewer";
|
|
59799
|
+
}
|
|
59800
|
+
function resultDoctype(result) {
|
|
59801
|
+
if (typeof result.doctype === "string") return result.doctype;
|
|
59802
|
+
const data = result.data;
|
|
59803
|
+
if (isRecord8(data) && typeof data.doctype === "string") return data.doctype;
|
|
59804
|
+
return void 0;
|
|
59805
|
+
}
|
|
59806
|
+
function resultDocument(result) {
|
|
59807
|
+
return isRecord8(result.data) ? result.data : void 0;
|
|
59808
|
+
}
|
|
59809
|
+
function isInvoiceViewer(result) {
|
|
59810
|
+
const uri = result._meta?.ui?.resourceUri;
|
|
59811
|
+
return uri === "ui://mcp-erpnext/invoice-viewer";
|
|
59812
|
+
}
|
|
59813
|
+
function isStockViewer(result) {
|
|
59814
|
+
const uri = result._meta?.ui?.resourceUri;
|
|
59815
|
+
return uri === "ui://mcp-erpnext/stock-viewer";
|
|
59816
|
+
}
|
|
59817
|
+
function isKanbanViewer(result) {
|
|
59818
|
+
const uri = result._meta?.ui?.resourceUri;
|
|
59819
|
+
return uri === "ui://mcp-erpnext/kanban-viewer";
|
|
59820
|
+
}
|
|
57150
59821
|
function isDoclistResult(result) {
|
|
57151
59822
|
return typeof result.doctype === "string" && Array.isArray(result.data);
|
|
57152
59823
|
}
|
|
@@ -57154,7 +59825,193 @@ function isDoclistViewer(result) {
|
|
|
57154
59825
|
const uri = result._meta?.ui?.resourceUri;
|
|
57155
59826
|
return uri === "ui://mcp-erpnext/doclist-viewer";
|
|
57156
59827
|
}
|
|
57157
|
-
function
|
|
59828
|
+
function isDocViewer(result) {
|
|
59829
|
+
const uri = result._meta?.ui?.resourceUri;
|
|
59830
|
+
return uri === "ui://mcp-erpnext/doc-viewer";
|
|
59831
|
+
}
|
|
59832
|
+
var VIEWER_TOOL_CANDIDATES = {
|
|
59833
|
+
"ui://mcp-erpnext/invoice-viewer": [
|
|
59834
|
+
"erpnext_item_get",
|
|
59835
|
+
"erpnext_stock_balance",
|
|
59836
|
+
"erpnext_file_list",
|
|
59837
|
+
"erpnext_file_upload",
|
|
59838
|
+
"erpnext_file_download"
|
|
59839
|
+
],
|
|
59840
|
+
"ui://mcp-erpnext/doclist-viewer": [],
|
|
59841
|
+
"ui://mcp-erpnext/doc-viewer": [
|
|
59842
|
+
"erpnext_file_list",
|
|
59843
|
+
"erpnext_file_upload",
|
|
59844
|
+
"erpnext_file_download"
|
|
59845
|
+
],
|
|
59846
|
+
"ui://mcp-erpnext/kanban-viewer": [
|
|
59847
|
+
"erpnext_doc_get",
|
|
59848
|
+
"erpnext_doc_update",
|
|
59849
|
+
"erpnext_user_list",
|
|
59850
|
+
"erpnext_doc_assign",
|
|
59851
|
+
"erpnext_doc_unassign"
|
|
59852
|
+
],
|
|
59853
|
+
"ui://mcp-erpnext/stock-viewer": [
|
|
59854
|
+
"erpnext_item_get",
|
|
59855
|
+
"erpnext_stock_entry_list"
|
|
59856
|
+
]
|
|
59857
|
+
};
|
|
59858
|
+
var SUBMITTABLE_VIEWER_DOCTYPES = /* @__PURE__ */ new Set([
|
|
59859
|
+
"Quotation",
|
|
59860
|
+
"Sales Order",
|
|
59861
|
+
"Delivery Note",
|
|
59862
|
+
"Sales Invoice",
|
|
59863
|
+
"Purchase Order",
|
|
59864
|
+
"Purchase Receipt",
|
|
59865
|
+
"Purchase Invoice",
|
|
59866
|
+
"Stock Entry",
|
|
59867
|
+
"Journal Entry",
|
|
59868
|
+
"Payment Entry",
|
|
59869
|
+
"Timesheet",
|
|
59870
|
+
"Leave Application",
|
|
59871
|
+
"Salary Slip",
|
|
59872
|
+
"BOM",
|
|
59873
|
+
"Work Order",
|
|
59874
|
+
"Job Card",
|
|
59875
|
+
"Asset",
|
|
59876
|
+
"Asset Movement",
|
|
59877
|
+
"Shipment"
|
|
59878
|
+
]);
|
|
59879
|
+
var INVOICE_DEDICATED_MUTATION_TOOLS = {
|
|
59880
|
+
"Sales Order": [
|
|
59881
|
+
"erpnext_sales_order_submit",
|
|
59882
|
+
"erpnext_sales_order_cancel"
|
|
59883
|
+
],
|
|
59884
|
+
"Sales Invoice": ["erpnext_sales_invoice_submit"]
|
|
59885
|
+
};
|
|
59886
|
+
function addDoctypeMutationCandidates(target, uri, doctype) {
|
|
59887
|
+
if (!doctype || !SUBMITTABLE_VIEWER_DOCTYPES.has(doctype)) return;
|
|
59888
|
+
if (uri === "ui://mcp-erpnext/invoice-viewer" || uri === "ui://mcp-erpnext/doclist-viewer" || uri === "ui://mcp-erpnext/doc-viewer") {
|
|
59889
|
+
target.add("erpnext_doc_submit");
|
|
59890
|
+
target.add("erpnext_doc_cancel");
|
|
59891
|
+
}
|
|
59892
|
+
if (uri === "ui://mcp-erpnext/invoice-viewer") {
|
|
59893
|
+
for (const tool of INVOICE_DEDICATED_MUTATION_TOOLS[doctype] ?? []) {
|
|
59894
|
+
target.add(tool);
|
|
59895
|
+
}
|
|
59896
|
+
}
|
|
59897
|
+
}
|
|
59898
|
+
function addJumpTools(target, jumps) {
|
|
59899
|
+
if (!jumps) return;
|
|
59900
|
+
for (const jump of Object.values(jumps)) target.add(jump.tool);
|
|
59901
|
+
}
|
|
59902
|
+
function addSeriesJumpTools(target, jumps) {
|
|
59903
|
+
if (!jumps) return;
|
|
59904
|
+
for (const seriesJumps of Object.values(jumps)) {
|
|
59905
|
+
addJumpTools(target, seriesJumps);
|
|
59906
|
+
}
|
|
59907
|
+
}
|
|
59908
|
+
function availableViewerToolNames(result, availableToolNames) {
|
|
59909
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
59910
|
+
const uri = result._meta?.ui?.resourceUri;
|
|
59911
|
+
for (const name of uri ? VIEWER_TOOL_CANDIDATES[uri] ?? [] : []) {
|
|
59912
|
+
candidates.add(name);
|
|
59913
|
+
}
|
|
59914
|
+
addDoctypeMutationCandidates(candidates, uri, resultDoctype(result));
|
|
59915
|
+
if (result.refreshRequest) candidates.add(result.refreshRequest.toolName);
|
|
59916
|
+
if (result._rowAction) candidates.add(result._rowAction.toolName);
|
|
59917
|
+
for (const hint of result._sendMessageHints ?? []) {
|
|
59918
|
+
if (hint.tool) candidates.add(hint.tool);
|
|
59919
|
+
}
|
|
59920
|
+
addJumpTools(
|
|
59921
|
+
candidates,
|
|
59922
|
+
result._jumps ? Object.fromEntries(
|
|
59923
|
+
Object.entries(result._jumps).filter(
|
|
59924
|
+
(entry) => entry[1] !== void 0
|
|
59925
|
+
)
|
|
59926
|
+
) : void 0
|
|
59927
|
+
);
|
|
59928
|
+
addJumpTools(candidates, result._stageJumps);
|
|
59929
|
+
addJumpTools(candidates, result._pointJumps);
|
|
59930
|
+
addSeriesJumpTools(candidates, result._seriesPointJumps);
|
|
59931
|
+
if (typeof result.moveToolName === "string") {
|
|
59932
|
+
candidates.add(result.moveToolName);
|
|
59933
|
+
}
|
|
59934
|
+
return [...candidates].filter((name) => availableToolNames.has(name)).sort();
|
|
59935
|
+
}
|
|
59936
|
+
function filterJumpMap(jumps, availableToolNames) {
|
|
59937
|
+
const available = Object.fromEntries(
|
|
59938
|
+
Object.entries(jumps).filter(
|
|
59939
|
+
([, jump]) => availableToolNames.has(jump.tool)
|
|
59940
|
+
)
|
|
59941
|
+
);
|
|
59942
|
+
return Object.keys(available).length > 0 ? available : void 0;
|
|
59943
|
+
}
|
|
59944
|
+
function filterSeriesJumpMap(jumps, availableToolNames) {
|
|
59945
|
+
const available = {};
|
|
59946
|
+
for (const [label, seriesJumps] of Object.entries(jumps)) {
|
|
59947
|
+
const filtered = filterJumpMap(seriesJumps, availableToolNames);
|
|
59948
|
+
if (filtered) available[label] = filtered;
|
|
59949
|
+
}
|
|
59950
|
+
return Object.keys(available).length > 0 ? available : void 0;
|
|
59951
|
+
}
|
|
59952
|
+
function filterNavJumpsByAvailableTools(result, availableToolNames) {
|
|
59953
|
+
if (!availableToolNames) return result;
|
|
59954
|
+
const filtered = { ...result };
|
|
59955
|
+
if (result._jumps) {
|
|
59956
|
+
const jumps = filterJumpMap(
|
|
59957
|
+
Object.fromEntries(
|
|
59958
|
+
Object.entries(result._jumps).filter(
|
|
59959
|
+
(entry) => entry[1] !== void 0
|
|
59960
|
+
)
|
|
59961
|
+
),
|
|
59962
|
+
availableToolNames
|
|
59963
|
+
);
|
|
59964
|
+
if (jumps) filtered._jumps = jumps;
|
|
59965
|
+
else delete filtered._jumps;
|
|
59966
|
+
}
|
|
59967
|
+
if (result._stageJumps) {
|
|
59968
|
+
const jumps = filterJumpMap(result._stageJumps, availableToolNames);
|
|
59969
|
+
if (jumps) filtered._stageJumps = jumps;
|
|
59970
|
+
else delete filtered._stageJumps;
|
|
59971
|
+
}
|
|
59972
|
+
if (result._pointJumps) {
|
|
59973
|
+
const jumps = filterJumpMap(result._pointJumps, availableToolNames);
|
|
59974
|
+
if (jumps) filtered._pointJumps = jumps;
|
|
59975
|
+
else delete filtered._pointJumps;
|
|
59976
|
+
}
|
|
59977
|
+
if (result._seriesPointJumps) {
|
|
59978
|
+
const jumps = filterSeriesJumpMap(
|
|
59979
|
+
result._seriesPointJumps,
|
|
59980
|
+
availableToolNames
|
|
59981
|
+
);
|
|
59982
|
+
if (jumps) filtered._seriesPointJumps = jumps;
|
|
59983
|
+
else delete filtered._seriesPointJumps;
|
|
59984
|
+
}
|
|
59985
|
+
if (result._rowAction && !availableToolNames.has(result._rowAction.toolName)) {
|
|
59986
|
+
const doctype = resultDoctype(result);
|
|
59987
|
+
if (doctype && availableToolNames.has("erpnext_doc_get")) {
|
|
59988
|
+
filtered._rowAction = {
|
|
59989
|
+
toolName: "erpnext_doc_get",
|
|
59990
|
+
idField: "name",
|
|
59991
|
+
argName: "name",
|
|
59992
|
+
extraArgs: { doctype }
|
|
59993
|
+
};
|
|
59994
|
+
} else {
|
|
59995
|
+
delete filtered._rowAction;
|
|
59996
|
+
}
|
|
59997
|
+
}
|
|
59998
|
+
if (result._sendMessageHints) {
|
|
59999
|
+
filtered._sendMessageHints = result._sendMessageHints.map((hint) => {
|
|
60000
|
+
if (!hint.tool || availableToolNames.has(hint.tool)) return hint;
|
|
60001
|
+
return { key: hint.key, label: hint.label, message: hint.message };
|
|
60002
|
+
});
|
|
60003
|
+
}
|
|
60004
|
+
filtered._availableTools = availableViewerToolNames(
|
|
60005
|
+
filtered,
|
|
60006
|
+
availableToolNames
|
|
60007
|
+
);
|
|
60008
|
+
return filtered;
|
|
60009
|
+
}
|
|
60010
|
+
function withViewerToolCapabilities(result, availableToolNames) {
|
|
60011
|
+
if (!isRecord8(result) || !hasUiResource(result)) return result;
|
|
60012
|
+
return filterNavJumpsByAvailableTools(result, availableToolNames);
|
|
60013
|
+
}
|
|
60014
|
+
function withUiRefreshRequest(result, toolName, args, now = /* @__PURE__ */ new Date(), availableToolNames) {
|
|
57158
60015
|
if (!isRecord8(result) || !hasUiResource(result)) {
|
|
57159
60016
|
return result;
|
|
57160
60017
|
}
|
|
@@ -57184,8 +60041,8 @@ function withUiRefreshRequest(result, toolName, args) {
|
|
|
57184
60041
|
}
|
|
57185
60042
|
}
|
|
57186
60043
|
if (isDoclistResult(enriched) && isDoclistViewer(enriched) && !enriched._sendMessageHints) {
|
|
57187
|
-
const hints =
|
|
57188
|
-
if (hints) {
|
|
60044
|
+
const hints = documentNavigationHints(enriched.doctype);
|
|
60045
|
+
if (hints.length > 0) {
|
|
57189
60046
|
enriched._sendMessageHints = hints;
|
|
57190
60047
|
}
|
|
57191
60048
|
}
|
|
@@ -57204,10 +60061,92 @@ function withUiRefreshRequest(result, toolName, args) {
|
|
|
57204
60061
|
}
|
|
57205
60062
|
}
|
|
57206
60063
|
}
|
|
57207
|
-
|
|
60064
|
+
if (isKpiViewer(enriched) && !enriched._jumps) {
|
|
60065
|
+
const jumps = KPI_JUMPS[toolName];
|
|
60066
|
+
if (jumps) enriched._jumps = jumps(monthRange(now));
|
|
60067
|
+
}
|
|
60068
|
+
if (isFunnelViewer(enriched) && !enriched._stageJumps) {
|
|
60069
|
+
enriched._stageJumps = funnelStageJumps(args, now);
|
|
60070
|
+
}
|
|
60071
|
+
if (isChartViewer(enriched) && Array.isArray(enriched.labels)) {
|
|
60072
|
+
const labels = enriched.labels.map(String);
|
|
60073
|
+
if (!enriched._pointJumps) {
|
|
60074
|
+
const jumps = chartPointJumps(toolName, args, labels, now);
|
|
60075
|
+
if (jumps) enriched._pointJumps = jumps;
|
|
60076
|
+
}
|
|
60077
|
+
if (!enriched._seriesPointJumps) {
|
|
60078
|
+
const jumps = chartSeriesPointJumps(toolName, args, labels, now);
|
|
60079
|
+
if (jumps) enriched._seriesPointJumps = jumps;
|
|
60080
|
+
}
|
|
60081
|
+
}
|
|
60082
|
+
if (!enriched._sendMessageHints) {
|
|
60083
|
+
const doctype = resultDoctype(enriched);
|
|
60084
|
+
if (isInvoiceViewer(enriched) && doctype) {
|
|
60085
|
+
const hints = invoiceNavigationHints(doctype, resultDocument(enriched));
|
|
60086
|
+
if (hints.length > 0) enriched._sendMessageHints = hints;
|
|
60087
|
+
} else if (isStockViewer(enriched)) {
|
|
60088
|
+
enriched._sendMessageHints = STOCK_HINTS;
|
|
60089
|
+
} else if ((isKanbanViewer(enriched) || isDocViewer(enriched)) && doctype) {
|
|
60090
|
+
const hints = isDocViewer(enriched) ? invoiceNavigationHints(doctype, resultDocument(enriched)) : documentNavigationHints(doctype, resultDocument(enriched));
|
|
60091
|
+
if (hints.length > 0) enriched._sendMessageHints = hints;
|
|
60092
|
+
}
|
|
60093
|
+
}
|
|
60094
|
+
return filterNavJumpsByAvailableTools(enriched, availableToolNames);
|
|
57208
60095
|
}
|
|
57209
60096
|
|
|
57210
60097
|
// src/client.ts
|
|
60098
|
+
function isRecord9(value) {
|
|
60099
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
60100
|
+
}
|
|
60101
|
+
function isSingleEmbeddedBlobResult(value) {
|
|
60102
|
+
if (!isRecord9(value) || !Array.isArray(value.content)) return false;
|
|
60103
|
+
if (Object.keys(value).some((key) => key !== "content")) return false;
|
|
60104
|
+
if (value.content.length !== 2) return false;
|
|
60105
|
+
const [summary, embedded] = value.content;
|
|
60106
|
+
if (!isRecord9(summary) || summary.type !== "text" || typeof summary.text !== "string") {
|
|
60107
|
+
return false;
|
|
60108
|
+
}
|
|
60109
|
+
if (!isRecord9(embedded) || embedded.type !== "resource") return false;
|
|
60110
|
+
if (!isRecord9(embedded.resource)) return false;
|
|
60111
|
+
const resource = embedded.resource;
|
|
60112
|
+
return typeof resource.uri === "string" && resource.uri.startsWith("file:///") && typeof resource.mimeType === "string" && typeof resource.blob === "string";
|
|
60113
|
+
}
|
|
60114
|
+
function withSafeUiRefresh(result, tool, args, availableToolNames) {
|
|
60115
|
+
const readOnly = tool.annotations?.readOnlyHint === true;
|
|
60116
|
+
if (!isRecord9(result)) {
|
|
60117
|
+
return result;
|
|
60118
|
+
}
|
|
60119
|
+
const resultMeta = isRecord9(result._meta) ? result._meta : {};
|
|
60120
|
+
const declaredUi = tool._meta?.ui;
|
|
60121
|
+
const viewerResult = declaredUi && !isRecord9(resultMeta.ui) ? {
|
|
60122
|
+
...result,
|
|
60123
|
+
_meta: { ...resultMeta, ...tool._meta, ui: declaredUi }
|
|
60124
|
+
} : result;
|
|
60125
|
+
const request = isRecord9(viewerResult.refreshRequest) ? viewerResult.refreshRequest : null;
|
|
60126
|
+
const target = request && typeof request.toolName === "string" ? getToolByName(request.toolName) : void 0;
|
|
60127
|
+
const hasSafeExplicitRefresh = request !== null && typeof request.toolName === "string" && availableToolNames.has(request.toolName) && isRecord9(request.arguments) && (request.toolName === tool.name ? readOnly : target?.annotations?.readOnlyHint === true);
|
|
60128
|
+
if (hasSafeExplicitRefresh) {
|
|
60129
|
+
return withUiRefreshRequest(
|
|
60130
|
+
viewerResult,
|
|
60131
|
+
tool.name,
|
|
60132
|
+
args,
|
|
60133
|
+
/* @__PURE__ */ new Date(),
|
|
60134
|
+
availableToolNames
|
|
60135
|
+
);
|
|
60136
|
+
}
|
|
60137
|
+
let sanitized = viewerResult;
|
|
60138
|
+
if ("refreshRequest" in viewerResult) {
|
|
60139
|
+
sanitized = { ...viewerResult };
|
|
60140
|
+
delete sanitized.refreshRequest;
|
|
60141
|
+
}
|
|
60142
|
+
return readOnly ? withUiRefreshRequest(
|
|
60143
|
+
sanitized,
|
|
60144
|
+
tool.name,
|
|
60145
|
+
args,
|
|
60146
|
+
/* @__PURE__ */ new Date(),
|
|
60147
|
+
availableToolNames
|
|
60148
|
+
) : withViewerToolCapabilities(sanitized, availableToolNames);
|
|
60149
|
+
}
|
|
57211
60150
|
var ErpNextToolsClient = class {
|
|
57212
60151
|
tools;
|
|
57213
60152
|
enableLinkDisambiguation;
|
|
@@ -57248,6 +60187,7 @@ var ErpNextToolsClient = class {
|
|
|
57248
60187
|
*/
|
|
57249
60188
|
buildHandlersMap() {
|
|
57250
60189
|
const handlers = /* @__PURE__ */ new Map();
|
|
60190
|
+
const availableToolNames = new Set(this.tools.map((tool) => tool.name));
|
|
57251
60191
|
for (const tool of this.tools) {
|
|
57252
60192
|
const toolMeta = tool._meta;
|
|
57253
60193
|
handlers.set(tool.name, async (args, mcpContext) => {
|
|
@@ -57270,10 +60210,14 @@ var ErpNextToolsClient = class {
|
|
|
57270
60210
|
if (execution.result !== null && typeof execution.result === "object" && !Array.isArray(execution.result) && execution.result.resultType === "input_required") {
|
|
57271
60211
|
return execution.result;
|
|
57272
60212
|
}
|
|
57273
|
-
|
|
60213
|
+
if (tool.name === "erpnext_file_download" && isSingleEmbeddedBlobResult(execution.result)) {
|
|
60214
|
+
return execution.result;
|
|
60215
|
+
}
|
|
60216
|
+
const result = withSafeUiRefresh(
|
|
57274
60217
|
execution.result,
|
|
57275
|
-
tool
|
|
57276
|
-
execution.args
|
|
60218
|
+
tool,
|
|
60219
|
+
execution.args,
|
|
60220
|
+
availableToolNames
|
|
57277
60221
|
);
|
|
57278
60222
|
const r = result !== null && typeof result === "object" && !Array.isArray(result) ? result : null;
|
|
57279
60223
|
const resultUi = r?._meta && typeof r._meta === "object" && r._meta.ui;
|
|
@@ -57300,7 +60244,15 @@ var ErpNextToolsClient = class {
|
|
|
57300
60244
|
}
|
|
57301
60245
|
const client = getFrappeClient();
|
|
57302
60246
|
const result = await tool.handler(args, { client });
|
|
57303
|
-
|
|
60247
|
+
if (tool.name === "erpnext_file_download" && isSingleEmbeddedBlobResult(result)) {
|
|
60248
|
+
return result;
|
|
60249
|
+
}
|
|
60250
|
+
return withSafeUiRefresh(
|
|
60251
|
+
result,
|
|
60252
|
+
tool,
|
|
60253
|
+
args,
|
|
60254
|
+
new Set(this.tools.map((candidate) => candidate.name))
|
|
60255
|
+
);
|
|
57304
60256
|
}
|
|
57305
60257
|
/** Get tool count */
|
|
57306
60258
|
get count() {
|
|
@@ -57314,11 +60266,128 @@ var UI_VIEWERS = [
|
|
|
57314
60266
|
"invoice-viewer",
|
|
57315
60267
|
"stock-viewer",
|
|
57316
60268
|
"doclist-viewer",
|
|
60269
|
+
"doc-viewer",
|
|
57317
60270
|
"chart-viewer",
|
|
57318
60271
|
"kpi-viewer",
|
|
57319
60272
|
"funnel-viewer",
|
|
57320
|
-
"kanban-viewer"
|
|
60273
|
+
"kanban-viewer",
|
|
60274
|
+
"buy-evidence-viewer"
|
|
57321
60275
|
];
|
|
60276
|
+
var VIEWER_RESOURCE_META = {
|
|
60277
|
+
ui: {
|
|
60278
|
+
csp: {
|
|
60279
|
+
resourceDomains: ["blob:"],
|
|
60280
|
+
frameDomains: ["blob:"]
|
|
60281
|
+
}
|
|
60282
|
+
}
|
|
60283
|
+
};
|
|
60284
|
+
function withViewerResourceMeta(resource) {
|
|
60285
|
+
return { ...resource, _meta: VIEWER_RESOURCE_META };
|
|
60286
|
+
}
|
|
60287
|
+
|
|
60288
|
+
// src/buy/manifest.ts
|
|
60289
|
+
var VIEW_APP_MANIFEST_SCHEMA = "io.casys.mcp.view-app-manifest/1.0";
|
|
60290
|
+
var VIEWER_SESSION_APPLY_ACTION = "viewer.session.apply";
|
|
60291
|
+
var BUY_VIEW_APP_MANIFEST = {
|
|
60292
|
+
schemaVersion: VIEW_APP_MANIFEST_SCHEMA,
|
|
60293
|
+
app: {
|
|
60294
|
+
id: BUY_VIEW_APP_ID,
|
|
60295
|
+
title: BUY_VIEW_APP_TITLE,
|
|
60296
|
+
version: BUY_VIEW_APP_VERSION
|
|
60297
|
+
},
|
|
60298
|
+
resources: [{
|
|
60299
|
+
uri: BUY_EVIDENCE_VIEWER_URI,
|
|
60300
|
+
ownership: "whole-view",
|
|
60301
|
+
resultSchemas: [BUY_RECORDED_RESULT_SCHEMA, BUY_RECORDED_RESULT_SCHEMA_V2],
|
|
60302
|
+
acceptedActions: [VIEWER_SESSION_APPLY_ACTION],
|
|
60303
|
+
sessionSchemas: [BUY_RECORDED_SESSION_SCHEMA]
|
|
60304
|
+
}]
|
|
60305
|
+
};
|
|
60306
|
+
var BUY_VIEW_APP_MANIFEST_JSON = `${JSON.stringify(BUY_VIEW_APP_MANIFEST)}
|
|
60307
|
+
`;
|
|
60308
|
+
function parseBuyViewAppManifest(value) {
|
|
60309
|
+
const root = exactRecord(
|
|
60310
|
+
value,
|
|
60311
|
+
["schemaVersion", "app", "resources"],
|
|
60312
|
+
"Buy View App manifest"
|
|
60313
|
+
);
|
|
60314
|
+
literal2(
|
|
60315
|
+
root.schemaVersion,
|
|
60316
|
+
VIEW_APP_MANIFEST_SCHEMA,
|
|
60317
|
+
"Buy View App manifest.schemaVersion"
|
|
60318
|
+
);
|
|
60319
|
+
const app = exactRecord(
|
|
60320
|
+
root.app,
|
|
60321
|
+
["id", "title", "version"],
|
|
60322
|
+
"Buy View App manifest.app"
|
|
60323
|
+
);
|
|
60324
|
+
literal2(app.id, BUY_VIEW_APP_ID, "Buy View App manifest.app.id");
|
|
60325
|
+
literal2(app.title, BUY_VIEW_APP_TITLE, "Buy View App manifest.app.title");
|
|
60326
|
+
literal2(
|
|
60327
|
+
app.version,
|
|
60328
|
+
BUY_VIEW_APP_VERSION,
|
|
60329
|
+
"Buy View App manifest.app.version"
|
|
60330
|
+
);
|
|
60331
|
+
const resources = denseArray(
|
|
60332
|
+
root.resources,
|
|
60333
|
+
"Buy View App manifest.resources"
|
|
60334
|
+
);
|
|
60335
|
+
if (resources.length !== 1) {
|
|
60336
|
+
throw new TypeError(
|
|
60337
|
+
"Buy View App manifest.resources must contain exactly one whole view."
|
|
60338
|
+
);
|
|
60339
|
+
}
|
|
60340
|
+
const resource = exactRecord(
|
|
60341
|
+
resources[0],
|
|
60342
|
+
[
|
|
60343
|
+
"uri",
|
|
60344
|
+
"ownership",
|
|
60345
|
+
"resultSchemas",
|
|
60346
|
+
"acceptedActions",
|
|
60347
|
+
"sessionSchemas"
|
|
60348
|
+
],
|
|
60349
|
+
"Buy View App manifest.resources[0]"
|
|
60350
|
+
);
|
|
60351
|
+
literal2(
|
|
60352
|
+
resource.uri,
|
|
60353
|
+
BUY_EVIDENCE_VIEWER_URI,
|
|
60354
|
+
"Buy View App manifest.resources[0].uri"
|
|
60355
|
+
);
|
|
60356
|
+
literal2(
|
|
60357
|
+
resource.ownership,
|
|
60358
|
+
"whole-view",
|
|
60359
|
+
"Buy View App manifest.resources[0].ownership"
|
|
60360
|
+
);
|
|
60361
|
+
const resultSchemas = denseArray(
|
|
60362
|
+
resource.resultSchemas,
|
|
60363
|
+
"Buy View App manifest.resources[0].resultSchemas"
|
|
60364
|
+
);
|
|
60365
|
+
if (resultSchemas.length !== 2 || resultSchemas[0] !== BUY_RECORDED_RESULT_SCHEMA || resultSchemas[1] !== BUY_RECORDED_RESULT_SCHEMA_V2) {
|
|
60366
|
+
throw new TypeError(
|
|
60367
|
+
"Buy View App manifest result schemas must list buy-recorded-result/1.0 then /2.0 exactly."
|
|
60368
|
+
);
|
|
60369
|
+
}
|
|
60370
|
+
const acceptedActions = denseArray(
|
|
60371
|
+
resource.acceptedActions,
|
|
60372
|
+
"Buy View App manifest.resources[0].acceptedActions"
|
|
60373
|
+
);
|
|
60374
|
+
if (acceptedActions.length !== 1 || acceptedActions[0] !== VIEWER_SESSION_APPLY_ACTION) {
|
|
60375
|
+
throw new TypeError(
|
|
60376
|
+
"Buy View App manifest must accept viewer.session.apply exactly."
|
|
60377
|
+
);
|
|
60378
|
+
}
|
|
60379
|
+
const sessionSchemas = denseArray(
|
|
60380
|
+
resource.sessionSchemas,
|
|
60381
|
+
"Buy View App manifest.resources[0].sessionSchemas"
|
|
60382
|
+
);
|
|
60383
|
+
if (sessionSchemas.length !== 1 || sessionSchemas[0] !== BUY_RECORDED_SESSION_SCHEMA) {
|
|
60384
|
+
throw new TypeError(
|
|
60385
|
+
"Buy View App manifest must bind buy-recorded-session/1.0 exactly."
|
|
60386
|
+
);
|
|
60387
|
+
}
|
|
60388
|
+
return BUY_VIEW_APP_MANIFEST;
|
|
60389
|
+
}
|
|
60390
|
+
parseBuyViewAppManifest(BUY_VIEW_APP_MANIFEST);
|
|
57322
60391
|
|
|
57323
60392
|
// src/ui/viewer-errors.ts
|
|
57324
60393
|
var ViewerResourceError = class extends Error {
|
|
@@ -57667,6 +60736,28 @@ function loadMrtrConfig() {
|
|
|
57667
60736
|
|
|
57668
60737
|
// server.ts
|
|
57669
60738
|
var DEFAULT_HTTP_PORT = 3012;
|
|
60739
|
+
function registerBuyViewAppManifest(server) {
|
|
60740
|
+
const bytes = new TextEncoder().encode(BUY_VIEW_APP_MANIFEST_JSON);
|
|
60741
|
+
server.registerResource(
|
|
60742
|
+
{
|
|
60743
|
+
uri: BUY_VIEW_APP_MANIFEST_URI,
|
|
60744
|
+
name: "ERPNext Buy Evidence App manifest",
|
|
60745
|
+
description: `Exact ${BUY_VIEW_APP_MANIFEST.app.id}@${BUY_VIEW_APP_MANIFEST.app.version} whole-view and recorded-session contract.`,
|
|
60746
|
+
mimeType: "application/json",
|
|
60747
|
+
size: bytes.byteLength
|
|
60748
|
+
},
|
|
60749
|
+
(requested) => {
|
|
60750
|
+
if (requested.toString() !== BUY_VIEW_APP_MANIFEST_URI) {
|
|
60751
|
+
throw new Error("Requested URI does not match the Buy App manifest.");
|
|
60752
|
+
}
|
|
60753
|
+
return {
|
|
60754
|
+
uri: BUY_VIEW_APP_MANIFEST_URI,
|
|
60755
|
+
mimeType: "application/json",
|
|
60756
|
+
text: BUY_VIEW_APP_MANIFEST_JSON
|
|
60757
|
+
};
|
|
60758
|
+
}
|
|
60759
|
+
);
|
|
60760
|
+
}
|
|
57670
60761
|
async function main() {
|
|
57671
60762
|
const args = getArgs3();
|
|
57672
60763
|
if (args.includes("--inspect")) {
|
|
@@ -57696,7 +60787,7 @@ async function main() {
|
|
|
57696
60787
|
);
|
|
57697
60788
|
const server = new McpApp({
|
|
57698
60789
|
name: "mcp-erpnext",
|
|
57699
|
-
version: "3.
|
|
60790
|
+
version: "3.2.0-beta.1",
|
|
57700
60791
|
transport: "stateless",
|
|
57701
60792
|
cache: {
|
|
57702
60793
|
ttlMs: 36e5,
|
|
@@ -57717,6 +60808,7 @@ async function main() {
|
|
|
57717
60808
|
const mcpTools = toolsClient.toMCPFormat();
|
|
57718
60809
|
const handlers = toolsClient.buildHandlersMap();
|
|
57719
60810
|
server.registerTools(mcpTools, handlers);
|
|
60811
|
+
registerBuyViewAppManifest(server);
|
|
57720
60812
|
const skippedRemoteViewers = [];
|
|
57721
60813
|
let skippedRemoteHost = "";
|
|
57722
60814
|
for (const viewerName of UI_VIEWERS) {
|
|
@@ -57733,15 +60825,19 @@ async function main() {
|
|
|
57733
60825
|
skippedRemoteHost = new URL(distPath).hostname;
|
|
57734
60826
|
} else if (distPath) {
|
|
57735
60827
|
server.registerResource(
|
|
57736
|
-
{
|
|
60828
|
+
withViewerResourceMeta({
|
|
57737
60829
|
uri: resourceUri,
|
|
57738
60830
|
name: `ERPNext ${humanName}`,
|
|
57739
60831
|
description: `ERPNext UI: ${viewerName}`,
|
|
57740
60832
|
mimeType: MCP_APP_MIME_TYPE
|
|
57741
|
-
},
|
|
60833
|
+
}),
|
|
57742
60834
|
async () => {
|
|
57743
60835
|
const html = await readViewerDist(distPath, readTextFile6);
|
|
57744
|
-
return {
|
|
60836
|
+
return withViewerResourceMeta({
|
|
60837
|
+
uri: resourceUri,
|
|
60838
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
60839
|
+
text: html
|
|
60840
|
+
});
|
|
57745
60841
|
}
|
|
57746
60842
|
);
|
|
57747
60843
|
console.error(`[mcp-erpnext] Registered UI resource: ${resourceUri}`);
|