@casys/mcp-erpnext 2.4.2 → 2.5.0
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 +42 -29
- package/mcp-erpnext.mjs +1270 -736
- package/package.json +1 -1
package/mcp-erpnext.mjs
CHANGED
|
@@ -37389,87 +37389,597 @@ var KANBAN_META = viewer("kanban-viewer");
|
|
|
37389
37389
|
var KPI_META = viewer("kpi-viewer");
|
|
37390
37390
|
var FUNNEL_META = viewer("funnel-viewer");
|
|
37391
37391
|
|
|
37392
|
-
// src/
|
|
37393
|
-
|
|
37394
|
-
|
|
37395
|
-
|
|
37396
|
-
|
|
37392
|
+
// src/runtime.ts
|
|
37393
|
+
var isDeno2 = typeof globalThis.Deno?.version?.deno === "string";
|
|
37394
|
+
var impl2 = isDeno2 ? await Promise.resolve().then(() => (init_runtime_deno2(), runtime_deno_exports2)) : await Promise.resolve().then(() => (init_runtime_node2(), runtime_node_exports2));
|
|
37395
|
+
var env6 = impl2.env;
|
|
37396
|
+
var readTextFile6 = impl2.readTextFile;
|
|
37397
|
+
var statSync3 = impl2.statSync;
|
|
37398
|
+
var readDirSync3 = impl2.readDirSync;
|
|
37399
|
+
var getArgs3 = impl2.getArgs;
|
|
37400
|
+
var exit3 = impl2.exit;
|
|
37401
|
+
var onSignal3 = impl2.onSignal;
|
|
37402
|
+
|
|
37403
|
+
// src/cache/memory.ts
|
|
37404
|
+
var MemoryCache = class {
|
|
37405
|
+
store = /* @__PURE__ */ new Map();
|
|
37406
|
+
get(key) {
|
|
37407
|
+
const entry = this.store.get(key);
|
|
37408
|
+
if (!entry) return void 0;
|
|
37409
|
+
if (Date.now() >= entry.expiresAt) {
|
|
37410
|
+
this.store.delete(key);
|
|
37411
|
+
return void 0;
|
|
37412
|
+
}
|
|
37413
|
+
return structuredClone(entry.value);
|
|
37414
|
+
}
|
|
37415
|
+
set(key, value, ttlMs) {
|
|
37416
|
+
this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
37417
|
+
}
|
|
37418
|
+
delete(key) {
|
|
37419
|
+
this.store.delete(key);
|
|
37420
|
+
}
|
|
37421
|
+
deleteByPrefix(prefix) {
|
|
37422
|
+
for (const key of this.store.keys()) {
|
|
37423
|
+
if (key.startsWith(prefix)) this.store.delete(key);
|
|
37424
|
+
}
|
|
37425
|
+
}
|
|
37426
|
+
clear() {
|
|
37427
|
+
this.store.clear();
|
|
37428
|
+
}
|
|
37429
|
+
};
|
|
37430
|
+
|
|
37431
|
+
// src/cache/noop.ts
|
|
37432
|
+
var NoopCache = class {
|
|
37433
|
+
get(_key) {
|
|
37434
|
+
return void 0;
|
|
37435
|
+
}
|
|
37436
|
+
set(_key, _value, _ttlMs) {
|
|
37437
|
+
}
|
|
37438
|
+
delete(_key) {
|
|
37439
|
+
}
|
|
37440
|
+
deleteByPrefix(_prefix) {
|
|
37441
|
+
}
|
|
37442
|
+
clear() {
|
|
37443
|
+
}
|
|
37444
|
+
};
|
|
37445
|
+
|
|
37446
|
+
// src/cache/cache.ts
|
|
37447
|
+
var DEFAULT_CACHE_TTL_MS = 15e3;
|
|
37448
|
+
var _cache = null;
|
|
37449
|
+
function getCacheTtlMs() {
|
|
37450
|
+
const raw2 = env6("MCP_CACHE_TTL_MS");
|
|
37451
|
+
if (raw2 === void 0) return DEFAULT_CACHE_TTL_MS;
|
|
37452
|
+
const parsed = Number(raw2);
|
|
37453
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
37454
|
+
console.error(
|
|
37455
|
+
`[mcp-erpnext] Invalid MCP_CACHE_TTL_MS=${JSON.stringify(raw2)} \u2014 must be a non-negative number. Falling back to default (${DEFAULT_CACHE_TTL_MS}ms).`
|
|
37397
37456
|
);
|
|
37457
|
+
return DEFAULT_CACHE_TTL_MS;
|
|
37398
37458
|
}
|
|
37399
|
-
return
|
|
37400
|
-
|
|
37401
|
-
|
|
37402
|
-
|
|
37403
|
-
|
|
37459
|
+
return parsed;
|
|
37460
|
+
}
|
|
37461
|
+
function getCache() {
|
|
37462
|
+
if (_cache) return _cache;
|
|
37463
|
+
const enabled = env6("MCP_CACHE_ENABLED") !== "false";
|
|
37464
|
+
_cache = enabled ? new MemoryCache() : new NoopCache();
|
|
37465
|
+
return _cache;
|
|
37466
|
+
}
|
|
37467
|
+
|
|
37468
|
+
// src/api/frappe-client.ts
|
|
37469
|
+
function stableStringify(value) {
|
|
37470
|
+
if (Array.isArray(value)) {
|
|
37471
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
37472
|
+
}
|
|
37473
|
+
if (value !== null && typeof value === "object") {
|
|
37474
|
+
const keys = Object.keys(value).sort();
|
|
37475
|
+
return `{${keys.map(
|
|
37476
|
+
(k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`
|
|
37477
|
+
).join(",")}}`;
|
|
37478
|
+
}
|
|
37479
|
+
return JSON.stringify(value);
|
|
37480
|
+
}
|
|
37481
|
+
var DEFAULT_RETRY_STATUSES = [408, 429, 502, 503, 504];
|
|
37482
|
+
var DEFAULT_RETRY_METHODS = ["GET"];
|
|
37483
|
+
var FrappeAPIError = class extends Error {
|
|
37484
|
+
/**
|
|
37485
|
+
* @param message - Human-readable error description
|
|
37486
|
+
* @param status - HTTP status code (0 for network errors, 408 for timeouts)
|
|
37487
|
+
* @param body - Raw response body (parsed JSON object or plain text string)
|
|
37488
|
+
* @param retryAfterMs - When the server sent a `Retry-After` header on a
|
|
37489
|
+
* retryable status (typically 429), the parsed delay
|
|
37490
|
+
* in ms. Used by the retry loop; absent otherwise.
|
|
37491
|
+
*/
|
|
37492
|
+
constructor(message2, status, body, retryAfterMs) {
|
|
37493
|
+
super(`[FrappeClient] ${message2} (HTTP ${status})`);
|
|
37494
|
+
this.status = status;
|
|
37495
|
+
this.body = body;
|
|
37496
|
+
this.retryAfterMs = retryAfterMs;
|
|
37497
|
+
this.name = "FrappeAPIError";
|
|
37498
|
+
}
|
|
37499
|
+
};
|
|
37500
|
+
function parseRetryAfter(raw2) {
|
|
37501
|
+
if (!raw2) return void 0;
|
|
37502
|
+
const trimmed = raw2.trim();
|
|
37503
|
+
const seconds = Number(trimmed);
|
|
37504
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
37505
|
+
return Math.round(seconds * 1e3);
|
|
37506
|
+
}
|
|
37507
|
+
const date3 = Date.parse(trimmed);
|
|
37508
|
+
if (Number.isFinite(date3)) {
|
|
37509
|
+
return Math.max(0, date3 - Date.now());
|
|
37510
|
+
}
|
|
37511
|
+
return void 0;
|
|
37512
|
+
}
|
|
37513
|
+
function extractServerMessages(raw2) {
|
|
37514
|
+
if (!raw2 || typeof raw2 !== "string") return void 0;
|
|
37515
|
+
try {
|
|
37516
|
+
const outer = JSON.parse(raw2);
|
|
37517
|
+
if (!Array.isArray(outer)) return void 0;
|
|
37518
|
+
const msgs = [];
|
|
37519
|
+
for (const item of outer) {
|
|
37520
|
+
try {
|
|
37521
|
+
const inner = JSON.parse(item);
|
|
37522
|
+
if (typeof inner === "object" && inner?.message) {
|
|
37523
|
+
msgs.push(inner.message);
|
|
37524
|
+
} else if (typeof inner === "string") {
|
|
37525
|
+
msgs.push(inner);
|
|
37526
|
+
}
|
|
37527
|
+
} catch {
|
|
37528
|
+
if (typeof item === "string") msgs.push(item);
|
|
37529
|
+
}
|
|
37404
37530
|
}
|
|
37405
|
-
|
|
37406
|
-
|
|
37407
|
-
|
|
37408
|
-
|
|
37531
|
+
return msgs.length > 0 ? msgs.join("; ") : void 0;
|
|
37532
|
+
} catch {
|
|
37533
|
+
return void 0;
|
|
37534
|
+
}
|
|
37535
|
+
}
|
|
37536
|
+
var FrappeClient = class {
|
|
37537
|
+
baseUrl;
|
|
37538
|
+
authHeader;
|
|
37539
|
+
timeoutMs;
|
|
37540
|
+
retries;
|
|
37541
|
+
retryStatuses;
|
|
37542
|
+
retryBackoffMs;
|
|
37543
|
+
retryMethods;
|
|
37544
|
+
cache;
|
|
37545
|
+
constructor(config2) {
|
|
37546
|
+
this.baseUrl = config2.baseUrl.replace(/\/$/, "");
|
|
37547
|
+
this.authHeader = `token ${config2.apiKey}:${config2.apiSecret}`;
|
|
37548
|
+
this.timeoutMs = config2.timeoutMs ?? 3e4;
|
|
37549
|
+
this.retries = config2.retries ?? 3;
|
|
37550
|
+
this.retryStatuses = config2.retryStatuses ?? DEFAULT_RETRY_STATUSES;
|
|
37551
|
+
this.retryBackoffMs = config2.retryBackoffMs ?? 200;
|
|
37552
|
+
this.retryMethods = config2.retryMethods ?? DEFAULT_RETRY_METHODS;
|
|
37553
|
+
this.cache = config2.cache ?? new MemoryCache();
|
|
37554
|
+
}
|
|
37555
|
+
// ── Private HTTP helpers ────────────────────────────────────────────────────
|
|
37556
|
+
buildHeaders() {
|
|
37557
|
+
return {
|
|
37558
|
+
"Authorization": this.authHeader,
|
|
37559
|
+
"Accept": "application/json",
|
|
37560
|
+
"Content-Type": "application/json"
|
|
37409
37561
|
};
|
|
37410
|
-
|
|
37411
|
-
|
|
37412
|
-
|
|
37413
|
-
|
|
37414
|
-
|
|
37562
|
+
}
|
|
37563
|
+
/**
|
|
37564
|
+
* Decide whether an error is worth retrying.
|
|
37565
|
+
* Network errors (status 0) and timeouts (status 408) are always retryable;
|
|
37566
|
+
* other statuses are checked against `retryStatuses`.
|
|
37567
|
+
*/
|
|
37568
|
+
isRetryable(err, method) {
|
|
37569
|
+
if (!this.retryMethods.includes(method)) return false;
|
|
37570
|
+
if (!(err instanceof FrappeAPIError)) return false;
|
|
37571
|
+
if (err.status === 0) return true;
|
|
37572
|
+
return this.retryStatuses.includes(err.status);
|
|
37573
|
+
}
|
|
37574
|
+
/** Compute the backoff delay for a given attempt (0-indexed). */
|
|
37575
|
+
computeBackoff(attempt, err) {
|
|
37576
|
+
if (err instanceof FrappeAPIError && err.retryAfterMs !== void 0) {
|
|
37577
|
+
return err.retryAfterMs;
|
|
37415
37578
|
}
|
|
37416
|
-
return
|
|
37417
|
-
}
|
|
37418
|
-
|
|
37419
|
-
|
|
37420
|
-
|
|
37421
|
-
|
|
37422
|
-
|
|
37423
|
-
|
|
37424
|
-
|
|
37425
|
-
|
|
37426
|
-
|
|
37427
|
-
|
|
37428
|
-
|
|
37429
|
-
|
|
37430
|
-
|
|
37431
|
-
customer_group: {
|
|
37432
|
-
type: "string",
|
|
37433
|
-
description: "Filter by customer group"
|
|
37434
|
-
},
|
|
37435
|
-
territory: { type: "string", description: "Filter by territory" },
|
|
37436
|
-
include_disabled: {
|
|
37437
|
-
type: "boolean",
|
|
37438
|
-
description: "Include disabled customers (default false)"
|
|
37579
|
+
return this.retryBackoffMs * 2 ** attempt;
|
|
37580
|
+
}
|
|
37581
|
+
async request(method, path, body) {
|
|
37582
|
+
let lastError;
|
|
37583
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
37584
|
+
try {
|
|
37585
|
+
return await this.requestOnce(method, path, body);
|
|
37586
|
+
} catch (err) {
|
|
37587
|
+
lastError = err;
|
|
37588
|
+
if (attempt === this.retries || !this.isRetryable(err, method)) {
|
|
37589
|
+
throw err;
|
|
37590
|
+
}
|
|
37591
|
+
const delay = this.computeBackoff(attempt, err);
|
|
37592
|
+
if (delay > 0) {
|
|
37593
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
37439
37594
|
}
|
|
37440
37595
|
}
|
|
37441
|
-
}
|
|
37442
|
-
|
|
37443
|
-
|
|
37444
|
-
|
|
37445
|
-
|
|
37446
|
-
|
|
37447
|
-
|
|
37448
|
-
|
|
37449
|
-
|
|
37596
|
+
}
|
|
37597
|
+
throw lastError;
|
|
37598
|
+
}
|
|
37599
|
+
async requestOnce(method, path, body) {
|
|
37600
|
+
const url = `${this.baseUrl}${path}`;
|
|
37601
|
+
const controller = new AbortController();
|
|
37602
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
37603
|
+
let response;
|
|
37604
|
+
try {
|
|
37605
|
+
response = await fetch(url, {
|
|
37606
|
+
method,
|
|
37607
|
+
headers: this.buildHeaders(),
|
|
37608
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
37609
|
+
signal: controller.signal
|
|
37610
|
+
});
|
|
37611
|
+
} catch (err) {
|
|
37612
|
+
clearTimeout(timer);
|
|
37613
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
37614
|
+
throw new FrappeAPIError(
|
|
37615
|
+
`Request timed out after ${this.timeoutMs}ms: ${method} ${path}`,
|
|
37616
|
+
408,
|
|
37617
|
+
null
|
|
37618
|
+
);
|
|
37450
37619
|
}
|
|
37451
|
-
|
|
37452
|
-
|
|
37620
|
+
throw new FrappeAPIError(
|
|
37621
|
+
`Network error on ${method} ${path}: ${err.message}`,
|
|
37622
|
+
0,
|
|
37623
|
+
null
|
|
37624
|
+
);
|
|
37625
|
+
}
|
|
37626
|
+
clearTimeout(timer);
|
|
37627
|
+
const rawText = await response.text();
|
|
37628
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
37629
|
+
let responseBody = rawText;
|
|
37630
|
+
if (contentType.includes("application/json") && rawText.length > 0) {
|
|
37631
|
+
try {
|
|
37632
|
+
responseBody = JSON.parse(rawText);
|
|
37633
|
+
} catch {
|
|
37634
|
+
responseBody = rawText;
|
|
37453
37635
|
}
|
|
37454
|
-
|
|
37455
|
-
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37459
|
-
|
|
37460
|
-
|
|
37461
|
-
|
|
37462
|
-
|
|
37463
|
-
|
|
37464
|
-
|
|
37465
|
-
|
|
37466
|
-
|
|
37467
|
-
|
|
37468
|
-
|
|
37469
|
-
|
|
37470
|
-
|
|
37471
|
-
|
|
37472
|
-
|
|
37636
|
+
}
|
|
37637
|
+
if (!response.ok) {
|
|
37638
|
+
let msg = response.statusText;
|
|
37639
|
+
if (typeof responseBody === "object" && responseBody !== null) {
|
|
37640
|
+
const rb = responseBody;
|
|
37641
|
+
const excType = rb.exc_type;
|
|
37642
|
+
const baseMsg = rb.message ?? excType ?? response.statusText;
|
|
37643
|
+
const serverDetails = extractServerMessages(rb._server_messages);
|
|
37644
|
+
msg = serverDetails ? `${baseMsg}: ${serverDetails}` : baseMsg;
|
|
37645
|
+
} else if (typeof responseBody === "string" && responseBody.length > 0) {
|
|
37646
|
+
msg = responseBody.slice(0, 200);
|
|
37647
|
+
}
|
|
37648
|
+
const retryAfterMs = parseRetryAfter(
|
|
37649
|
+
response.headers.get("retry-after")
|
|
37650
|
+
);
|
|
37651
|
+
throw new FrappeAPIError(
|
|
37652
|
+
`${method} ${path} failed: ${msg}`,
|
|
37653
|
+
response.status,
|
|
37654
|
+
responseBody,
|
|
37655
|
+
retryAfterMs
|
|
37656
|
+
);
|
|
37657
|
+
}
|
|
37658
|
+
return responseBody;
|
|
37659
|
+
}
|
|
37660
|
+
// ── Resource CRUD ───────────────────────────────────────────────────────────
|
|
37661
|
+
/**
|
|
37662
|
+
* List documents of a DocType.
|
|
37663
|
+
* Frappe list API: GET /api/resource/{doctype}?fields=...&filters=...
|
|
37664
|
+
*
|
|
37665
|
+
* Pass `{ skipCache: true }` to force a fresh read — e.g. for aggregate/KPI
|
|
37666
|
+
* tools that read across doctypes other than the one a preceding mutation
|
|
37667
|
+
* invalidated (see `invalidate()` below for why that gap exists). The fresh
|
|
37668
|
+
* result still refreshes the cache for subsequent normal reads.
|
|
37669
|
+
*/
|
|
37670
|
+
async list(doctype, options = {}, opts = {}) {
|
|
37671
|
+
const cacheKey = `list:${doctype}:${stableStringify(options)}`;
|
|
37672
|
+
if (!opts.skipCache) {
|
|
37673
|
+
const cached2 = this.cache.get(cacheKey);
|
|
37674
|
+
if (cached2 !== void 0) return cached2;
|
|
37675
|
+
}
|
|
37676
|
+
const params = new URLSearchParams();
|
|
37677
|
+
if (options.fields && options.fields.length > 0) {
|
|
37678
|
+
params.set("fields", JSON.stringify(options.fields));
|
|
37679
|
+
}
|
|
37680
|
+
if (options.filters && options.filters.length > 0) {
|
|
37681
|
+
params.set("filters", JSON.stringify(options.filters));
|
|
37682
|
+
}
|
|
37683
|
+
if (options.order_by) {
|
|
37684
|
+
params.set("order_by", options.order_by);
|
|
37685
|
+
}
|
|
37686
|
+
if (options.limit !== void 0) {
|
|
37687
|
+
params.set("limit", String(options.limit));
|
|
37688
|
+
}
|
|
37689
|
+
if (options.limit_start !== void 0) {
|
|
37690
|
+
params.set("limit_start", String(options.limit_start));
|
|
37691
|
+
}
|
|
37692
|
+
params.set("as_dict", "1");
|
|
37693
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
37694
|
+
const res = await this.request(
|
|
37695
|
+
"GET",
|
|
37696
|
+
`/api/resource/${encodeURIComponent(doctype)}${query}`
|
|
37697
|
+
);
|
|
37698
|
+
const docs = res.data ?? [];
|
|
37699
|
+
this.cache.set(cacheKey, docs, getCacheTtlMs());
|
|
37700
|
+
return docs;
|
|
37701
|
+
}
|
|
37702
|
+
/**
|
|
37703
|
+
* Get a single document by name.
|
|
37704
|
+
* GET /api/resource/{doctype}/{name}
|
|
37705
|
+
*
|
|
37706
|
+
* Pass `{ skipCache: true }` to force a fresh read — required before any
|
|
37707
|
+
* operation relying on the doc's `modified` timestamp for optimistic
|
|
37708
|
+
* locking (see erpnext_doc_submit/erpnext_doc_cancel in operations.ts).
|
|
37709
|
+
* The fresh result still refreshes the cache for subsequent normal reads.
|
|
37710
|
+
*/
|
|
37711
|
+
async get(doctype, name, opts = {}) {
|
|
37712
|
+
const cacheKey = `get:${doctype}:${name}`;
|
|
37713
|
+
if (!opts.skipCache) {
|
|
37714
|
+
const cached2 = this.cache.get(cacheKey);
|
|
37715
|
+
if (cached2 !== void 0) return cached2;
|
|
37716
|
+
}
|
|
37717
|
+
const res = await this.request(
|
|
37718
|
+
"GET",
|
|
37719
|
+
`/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(name)}`
|
|
37720
|
+
);
|
|
37721
|
+
this.cache.set(cacheKey, res.data, getCacheTtlMs());
|
|
37722
|
+
return res.data;
|
|
37723
|
+
}
|
|
37724
|
+
/**
|
|
37725
|
+
* Clear cached entries for a doctype (list results, resolveLink's
|
|
37726
|
+
* negative-match cache) and, if `name` is given, the cached single-document
|
|
37727
|
+
* read too. Called automatically after create/update/delete; call explicitly
|
|
37728
|
+
* after any mutation that bypasses those methods (e.g.
|
|
37729
|
+
* frappe.client.submit/cancel via callMethod).
|
|
37730
|
+
*
|
|
37731
|
+
* Known limitation: this only clears the mutated doctype. Frappe mutations
|
|
37732
|
+
* commonly cascade — submitting a Sales Order also writes Bin/GL
|
|
37733
|
+
* Entry/Sales Invoice rows — and those doctypes' cached `list()` results
|
|
37734
|
+
* aren't invalidated here. Aggregate/KPI tools that read across doctypes
|
|
37735
|
+
* can therefore serve up-to-TTL-stale numbers right after a mutation; pass
|
|
37736
|
+
* `{ skipCache: true }` to `list()` in those tools if that staleness isn't
|
|
37737
|
+
* acceptable for a given call site.
|
|
37738
|
+
*/
|
|
37739
|
+
invalidate(doctype, name) {
|
|
37740
|
+
this.cache.deleteByPrefix(`list:${doctype}:`);
|
|
37741
|
+
this.cache.deleteByPrefix(`resolve:miss:${doctype}:`);
|
|
37742
|
+
if (name) this.cache.delete(`get:${doctype}:${name}`);
|
|
37743
|
+
}
|
|
37744
|
+
/**
|
|
37745
|
+
* Create a new document.
|
|
37746
|
+
* POST /api/resource/{doctype}
|
|
37747
|
+
*/
|
|
37748
|
+
async create(doctype, data) {
|
|
37749
|
+
const res = await this.request(
|
|
37750
|
+
"POST",
|
|
37751
|
+
`/api/resource/${encodeURIComponent(doctype)}`,
|
|
37752
|
+
{ data: { ...data, doctype } }
|
|
37753
|
+
);
|
|
37754
|
+
this.invalidate(doctype, res.data.name);
|
|
37755
|
+
return res.data;
|
|
37756
|
+
}
|
|
37757
|
+
/**
|
|
37758
|
+
* Update an existing document (partial update).
|
|
37759
|
+
* PUT /api/resource/{doctype}/{name}
|
|
37760
|
+
*/
|
|
37761
|
+
async update(doctype, name, data) {
|
|
37762
|
+
const res = await this.request(
|
|
37763
|
+
"PUT",
|
|
37764
|
+
`/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(name)}`,
|
|
37765
|
+
{ data }
|
|
37766
|
+
);
|
|
37767
|
+
this.invalidate(doctype, name);
|
|
37768
|
+
return res.data;
|
|
37769
|
+
}
|
|
37770
|
+
/**
|
|
37771
|
+
* Delete a document.
|
|
37772
|
+
* DELETE /api/resource/{doctype}/{name}
|
|
37773
|
+
*/
|
|
37774
|
+
async delete(doctype, name) {
|
|
37775
|
+
await this.request(
|
|
37776
|
+
"DELETE",
|
|
37777
|
+
`/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(name)}`
|
|
37778
|
+
);
|
|
37779
|
+
this.invalidate(doctype, name);
|
|
37780
|
+
}
|
|
37781
|
+
/**
|
|
37782
|
+
* Call a whitelisted Frappe method.
|
|
37783
|
+
* POST /api/method/{method}
|
|
37784
|
+
*/
|
|
37785
|
+
async callMethod(method, args = {}) {
|
|
37786
|
+
const res = await this.request(
|
|
37787
|
+
"POST",
|
|
37788
|
+
`/api/method/${method}`,
|
|
37789
|
+
args
|
|
37790
|
+
);
|
|
37791
|
+
return res.message;
|
|
37792
|
+
}
|
|
37793
|
+
};
|
|
37794
|
+
var _client = null;
|
|
37795
|
+
function getFrappeClient() {
|
|
37796
|
+
if (_client) return _client;
|
|
37797
|
+
const url = env6("ERPNEXT_URL");
|
|
37798
|
+
const apiKey = env6("ERPNEXT_API_KEY");
|
|
37799
|
+
const apiSecret = env6("ERPNEXT_API_SECRET");
|
|
37800
|
+
if (!url) {
|
|
37801
|
+
throw new Error(
|
|
37802
|
+
"[lib/erpnext] ERPNEXT_URL is required. Set it to your ERPNext instance URL, e.g. http://localhost:8000"
|
|
37803
|
+
);
|
|
37804
|
+
}
|
|
37805
|
+
if (!apiKey || !apiSecret) {
|
|
37806
|
+
throw new Error(
|
|
37807
|
+
"[lib/erpnext] ERPNEXT_API_KEY and ERPNEXT_API_SECRET are required. Generate them in ERPNext: User Settings \u2192 API Access."
|
|
37808
|
+
);
|
|
37809
|
+
}
|
|
37810
|
+
_client = new FrappeClient({
|
|
37811
|
+
baseUrl: url,
|
|
37812
|
+
apiKey,
|
|
37813
|
+
apiSecret,
|
|
37814
|
+
cache: getCache()
|
|
37815
|
+
});
|
|
37816
|
+
return _client;
|
|
37817
|
+
}
|
|
37818
|
+
|
|
37819
|
+
// src/api/resolve.ts
|
|
37820
|
+
var NEGATIVE_CACHE_TTL_MS = 15e3;
|
|
37821
|
+
var EXACT_MATCH_PROBE_LIMIT = 2;
|
|
37822
|
+
var PARTIAL_MATCH_PROBE_LIMIT = 5;
|
|
37823
|
+
async function resolveUnique(client, doctype, identifier, searchField, op, value, probeLimit, ambiguityHint) {
|
|
37824
|
+
const rows = await client.list(doctype, {
|
|
37825
|
+
filters: [[searchField, op, value]],
|
|
37826
|
+
fields: ["name", searchField],
|
|
37827
|
+
limit: probeLimit
|
|
37828
|
+
});
|
|
37829
|
+
if (rows.length === 0) return void 0;
|
|
37830
|
+
if (rows.length === 1) return rows[0].name;
|
|
37831
|
+
const candidates = rows.map((r) => `${r.name} (${r[searchField]})`).join(
|
|
37832
|
+
", "
|
|
37833
|
+
);
|
|
37834
|
+
const suffix = rows.length === probeLimit ? ", and possibly more" : "";
|
|
37835
|
+
throw new Error(
|
|
37836
|
+
`[resolveLink] Ambiguous ${doctype} identifier "${identifier}": did you mean ${candidates}${suffix}? ${ambiguityHint}`
|
|
37837
|
+
);
|
|
37838
|
+
}
|
|
37839
|
+
async function resolveLink(client, doctype, identifier, searchField, options = {}) {
|
|
37840
|
+
const { allowPartialMatch = true } = options;
|
|
37841
|
+
const cache2 = getCache();
|
|
37842
|
+
const missKey = `resolve:miss:${doctype}:${identifier}`;
|
|
37843
|
+
if (cache2.get(missKey) === void 0) {
|
|
37844
|
+
try {
|
|
37845
|
+
await client.get(doctype, identifier);
|
|
37846
|
+
return identifier;
|
|
37847
|
+
} catch (e) {
|
|
37848
|
+
if (!(e instanceof FrappeAPIError) || e.status !== 404) throw e;
|
|
37849
|
+
cache2.set(missKey, true, NEGATIVE_CACHE_TTL_MS);
|
|
37850
|
+
}
|
|
37851
|
+
}
|
|
37852
|
+
const exact = await resolveUnique(
|
|
37853
|
+
client,
|
|
37854
|
+
doctype,
|
|
37855
|
+
identifier,
|
|
37856
|
+
searchField,
|
|
37857
|
+
"=",
|
|
37858
|
+
identifier,
|
|
37859
|
+
EXACT_MATCH_PROBE_LIMIT,
|
|
37860
|
+
"Please pass the record's ID directly."
|
|
37861
|
+
);
|
|
37862
|
+
if (exact !== void 0) return exact;
|
|
37863
|
+
if (allowPartialMatch) {
|
|
37864
|
+
const partial2 = await resolveUnique(
|
|
37865
|
+
client,
|
|
37866
|
+
doctype,
|
|
37867
|
+
identifier,
|
|
37868
|
+
searchField,
|
|
37869
|
+
"like",
|
|
37870
|
+
`%${identifier}%`,
|
|
37871
|
+
PARTIAL_MATCH_PROBE_LIMIT,
|
|
37872
|
+
"Please pass an exact value."
|
|
37873
|
+
);
|
|
37874
|
+
if (partial2 !== void 0) return partial2;
|
|
37875
|
+
}
|
|
37876
|
+
throw new Error(`[resolveLink] No ${doctype} found matching "${identifier}"`);
|
|
37877
|
+
}
|
|
37878
|
+
function resolveEmployee(client, identifier) {
|
|
37879
|
+
return resolveLink(client, "Employee", identifier, "employee_name");
|
|
37880
|
+
}
|
|
37881
|
+
function resolveCustomer(client, identifier) {
|
|
37882
|
+
return resolveLink(client, "Customer", identifier, "customer_name");
|
|
37883
|
+
}
|
|
37884
|
+
function resolveSupplier(client, identifier) {
|
|
37885
|
+
return resolveLink(client, "Supplier", identifier, "supplier_name");
|
|
37886
|
+
}
|
|
37887
|
+
function resolveItem(client, identifier) {
|
|
37888
|
+
return resolveLink(client, "Item", identifier, "item_name");
|
|
37889
|
+
}
|
|
37890
|
+
var DYNAMIC_LINK_SEARCH_FIELDS = {
|
|
37891
|
+
Customer: "customer_name",
|
|
37892
|
+
Supplier: "supplier_name",
|
|
37893
|
+
Employee: "employee_name",
|
|
37894
|
+
Lead: "lead_name"
|
|
37895
|
+
};
|
|
37896
|
+
async function resolveDynamicLink(client, targetDoctype, identifier, options = {}) {
|
|
37897
|
+
const searchField = DYNAMIC_LINK_SEARCH_FIELDS[targetDoctype];
|
|
37898
|
+
if (!searchField) return identifier;
|
|
37899
|
+
return resolveLink(client, targetDoctype, identifier, searchField, options);
|
|
37900
|
+
}
|
|
37901
|
+
|
|
37902
|
+
// src/tools/sales.ts
|
|
37903
|
+
function mapLineItems(items, options) {
|
|
37904
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
37905
|
+
throw new Error(
|
|
37906
|
+
`[${options.toolName}] 'items' must be a non-empty array`
|
|
37907
|
+
);
|
|
37908
|
+
}
|
|
37909
|
+
return items.map((item) => {
|
|
37910
|
+
if (!item.item_code || item.qty == null || item.rate == null) {
|
|
37911
|
+
throw new Error(
|
|
37912
|
+
`[${options.toolName}] Each item must have item_code, qty, and rate`
|
|
37913
|
+
);
|
|
37914
|
+
}
|
|
37915
|
+
const mapped = {
|
|
37916
|
+
item_code: item.item_code,
|
|
37917
|
+
qty: item.qty,
|
|
37918
|
+
rate: item.rate
|
|
37919
|
+
};
|
|
37920
|
+
if (options.defaultDeliveryDate !== void 0) {
|
|
37921
|
+
mapped.delivery_date = options.defaultDeliveryDate;
|
|
37922
|
+
}
|
|
37923
|
+
if (options.includeWarehouse !== false && item.warehouse) {
|
|
37924
|
+
mapped.warehouse = item.warehouse;
|
|
37925
|
+
}
|
|
37926
|
+
return mapped;
|
|
37927
|
+
});
|
|
37928
|
+
}
|
|
37929
|
+
var salesTools = [
|
|
37930
|
+
// ── Customers ─────────────────────────────────────────────────────────────
|
|
37931
|
+
{
|
|
37932
|
+
name: "erpnext_customer_list",
|
|
37933
|
+
annotations: { readOnlyHint: true },
|
|
37934
|
+
_meta: DOCLIST_META,
|
|
37935
|
+
description: "List ERPNext customers. Returns active customers by default. Fields: name, customer_name, customer_group, territory, email_id, disabled.",
|
|
37936
|
+
category: "sales",
|
|
37937
|
+
inputSchema: {
|
|
37938
|
+
type: "object",
|
|
37939
|
+
properties: {
|
|
37940
|
+
limit: { type: "number", description: "Max results (default 20)" },
|
|
37941
|
+
customer_group: {
|
|
37942
|
+
type: "string",
|
|
37943
|
+
description: "Filter by customer group"
|
|
37944
|
+
},
|
|
37945
|
+
territory: { type: "string", description: "Filter by territory" },
|
|
37946
|
+
include_disabled: {
|
|
37947
|
+
type: "boolean",
|
|
37948
|
+
description: "Include disabled customers (default false)"
|
|
37949
|
+
}
|
|
37950
|
+
}
|
|
37951
|
+
},
|
|
37952
|
+
handler: async (input, ctx) => {
|
|
37953
|
+
const limit = input.limit ?? 20;
|
|
37954
|
+
const filters = [];
|
|
37955
|
+
if (!input.include_disabled) {
|
|
37956
|
+
filters.push(["disabled", "=", 0]);
|
|
37957
|
+
}
|
|
37958
|
+
if (input.customer_group) {
|
|
37959
|
+
filters.push(["customer_group", "=", input.customer_group]);
|
|
37960
|
+
}
|
|
37961
|
+
if (input.territory) {
|
|
37962
|
+
filters.push(["territory", "=", input.territory]);
|
|
37963
|
+
}
|
|
37964
|
+
const docs = await ctx.client.list("Customer", {
|
|
37965
|
+
fields: [
|
|
37966
|
+
"name",
|
|
37967
|
+
"customer_name",
|
|
37968
|
+
"customer_group",
|
|
37969
|
+
"territory",
|
|
37970
|
+
"email_id",
|
|
37971
|
+
"disabled"
|
|
37972
|
+
],
|
|
37973
|
+
filters,
|
|
37974
|
+
limit,
|
|
37975
|
+
order_by: "modified desc"
|
|
37976
|
+
});
|
|
37977
|
+
return {
|
|
37978
|
+
doctype: "Customer",
|
|
37979
|
+
count: docs.length,
|
|
37980
|
+
data: docs,
|
|
37981
|
+
_meta: DOCLIST_META
|
|
37982
|
+
};
|
|
37473
37983
|
}
|
|
37474
37984
|
},
|
|
37475
37985
|
{
|
|
@@ -37592,7 +38102,10 @@ var salesTools = [
|
|
|
37592
38102
|
type: "object",
|
|
37593
38103
|
properties: {
|
|
37594
38104
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
37595
|
-
customer: {
|
|
38105
|
+
customer: {
|
|
38106
|
+
type: "string",
|
|
38107
|
+
description: "Filter by customer ID or name (e.g. 'CUST-00001' or 'Acme Corp')"
|
|
38108
|
+
},
|
|
37596
38109
|
status: {
|
|
37597
38110
|
type: "string",
|
|
37598
38111
|
description: "Filter by status (Draft, To Deliver and Bill, Completed, Cancelled, etc.)"
|
|
@@ -37608,7 +38121,11 @@ var salesTools = [
|
|
|
37608
38121
|
const limit = input.limit ?? 20;
|
|
37609
38122
|
const filters = [];
|
|
37610
38123
|
if (input.customer) {
|
|
37611
|
-
filters.push([
|
|
38124
|
+
filters.push([
|
|
38125
|
+
"customer",
|
|
38126
|
+
"=",
|
|
38127
|
+
await resolveCustomer(ctx.client, input.customer)
|
|
38128
|
+
]);
|
|
37612
38129
|
}
|
|
37613
38130
|
if (input.status) {
|
|
37614
38131
|
filters.push(["status", "=", input.status]);
|
|
@@ -37872,7 +38389,10 @@ var salesTools = [
|
|
|
37872
38389
|
type: "object",
|
|
37873
38390
|
properties: {
|
|
37874
38391
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
37875
|
-
customer: {
|
|
38392
|
+
customer: {
|
|
38393
|
+
type: "string",
|
|
38394
|
+
description: "Filter by customer ID or name (e.g. 'CUST-00001' or 'Acme Corp')"
|
|
38395
|
+
},
|
|
37876
38396
|
status: {
|
|
37877
38397
|
type: "string",
|
|
37878
38398
|
description: "Filter by status (Draft, Unpaid, Paid, Overdue, Cancelled, etc.)"
|
|
@@ -37888,7 +38408,11 @@ var salesTools = [
|
|
|
37888
38408
|
const limit = input.limit ?? 20;
|
|
37889
38409
|
const filters = [];
|
|
37890
38410
|
if (input.customer) {
|
|
37891
|
-
filters.push([
|
|
38411
|
+
filters.push([
|
|
38412
|
+
"customer",
|
|
38413
|
+
"=",
|
|
38414
|
+
await resolveCustomer(ctx.client, input.customer)
|
|
38415
|
+
]);
|
|
37892
38416
|
}
|
|
37893
38417
|
if (input.status) {
|
|
37894
38418
|
filters.push(["status", "=", input.status]);
|
|
@@ -38076,22 +38600,54 @@ var salesTools = [
|
|
|
38076
38600
|
type: "object",
|
|
38077
38601
|
properties: {
|
|
38078
38602
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
38079
|
-
|
|
38080
|
-
status: {
|
|
38603
|
+
quotation_to: {
|
|
38081
38604
|
type: "string",
|
|
38082
|
-
description: "
|
|
38083
|
-
|
|
38084
|
-
|
|
38085
|
-
|
|
38605
|
+
description: "Party type: Customer or Lead. Required when 'party_name' is set, so the party name/ID can be resolved against the right doctype.",
|
|
38606
|
+
enum: ["Customer", "Lead"]
|
|
38607
|
+
},
|
|
38608
|
+
party_name: {
|
|
38609
|
+
type: "string",
|
|
38610
|
+
description: "Filter by party \u2014 ID or name (e.g. customer/lead name). Requires 'quotation_to'."
|
|
38611
|
+
},
|
|
38612
|
+
status: {
|
|
38613
|
+
type: "string",
|
|
38614
|
+
description: "Filter by status (Draft, Open, Replied, Ordered, Lost, Cancelled)"
|
|
38615
|
+
},
|
|
38616
|
+
date_from: {
|
|
38617
|
+
type: "string",
|
|
38618
|
+
description: "Start date filter YYYY-MM-DD"
|
|
38619
|
+
},
|
|
38620
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
38621
|
+
}
|
|
38622
|
+
},
|
|
38086
38623
|
handler: async (input, ctx) => {
|
|
38087
38624
|
const limit = input.limit ?? 20;
|
|
38088
38625
|
const filters = [];
|
|
38089
38626
|
if (input.party_name) {
|
|
38090
|
-
|
|
38627
|
+
if (!input.quotation_to) {
|
|
38628
|
+
throw new Error(
|
|
38629
|
+
"[erpnext_quotation_list] 'quotation_to' is required when filtering by 'party_name'"
|
|
38630
|
+
);
|
|
38631
|
+
}
|
|
38632
|
+
filters.push([
|
|
38633
|
+
"party_name",
|
|
38634
|
+
"=",
|
|
38635
|
+
await resolveDynamicLink(
|
|
38636
|
+
ctx.client,
|
|
38637
|
+
input.quotation_to,
|
|
38638
|
+
input.party_name
|
|
38639
|
+
)
|
|
38640
|
+
]);
|
|
38091
38641
|
}
|
|
38092
38642
|
if (input.status) {
|
|
38093
38643
|
filters.push(["status", "=", input.status]);
|
|
38094
38644
|
}
|
|
38645
|
+
if (input.date_from) {
|
|
38646
|
+
filters.push(["transaction_date", ">=", input.date_from]);
|
|
38647
|
+
}
|
|
38648
|
+
if (input.date_to) {
|
|
38649
|
+
filters.push(["transaction_date", "<=", input.date_to]);
|
|
38650
|
+
}
|
|
38095
38651
|
const docs = await ctx.client.list("Quotation", {
|
|
38096
38652
|
fields: [
|
|
38097
38653
|
"name",
|
|
@@ -38151,7 +38707,7 @@ var salesTools = [
|
|
|
38151
38707
|
},
|
|
38152
38708
|
party_name: {
|
|
38153
38709
|
type: "string",
|
|
38154
|
-
description: "Customer or Lead name"
|
|
38710
|
+
description: "Customer or Lead \u2014 ID or name"
|
|
38155
38711
|
},
|
|
38156
38712
|
items: {
|
|
38157
38713
|
type: "array",
|
|
@@ -38204,7 +38760,12 @@ var salesTools = [
|
|
|
38204
38760
|
});
|
|
38205
38761
|
const data = {
|
|
38206
38762
|
quotation_to: input.quotation_to,
|
|
38207
|
-
party_name:
|
|
38763
|
+
party_name: await resolveDynamicLink(
|
|
38764
|
+
ctx.client,
|
|
38765
|
+
input.quotation_to,
|
|
38766
|
+
input.party_name,
|
|
38767
|
+
{ allowPartialMatch: false }
|
|
38768
|
+
),
|
|
38208
38769
|
items
|
|
38209
38770
|
};
|
|
38210
38771
|
if (input.transaction_date) {
|
|
@@ -38418,7 +38979,10 @@ var inventoryTools = [
|
|
|
38418
38979
|
type: "object",
|
|
38419
38980
|
properties: {
|
|
38420
38981
|
limit: { type: "number", description: "Max results (default 50)" },
|
|
38421
|
-
item_code: {
|
|
38982
|
+
item_code: {
|
|
38983
|
+
type: "string",
|
|
38984
|
+
description: "Filter by item code or name (e.g. 'ITEM-001' or 'Widget A')"
|
|
38985
|
+
},
|
|
38422
38986
|
warehouse: { type: "string", description: "Filter by warehouse" }
|
|
38423
38987
|
}
|
|
38424
38988
|
},
|
|
@@ -38426,7 +38990,11 @@ var inventoryTools = [
|
|
|
38426
38990
|
const limit = input.limit ?? 50;
|
|
38427
38991
|
const filters = [];
|
|
38428
38992
|
if (input.item_code) {
|
|
38429
|
-
filters.push([
|
|
38993
|
+
filters.push([
|
|
38994
|
+
"item_code",
|
|
38995
|
+
"=",
|
|
38996
|
+
await resolveItem(ctx.client, input.item_code)
|
|
38997
|
+
]);
|
|
38430
38998
|
}
|
|
38431
38999
|
if (input.warehouse) {
|
|
38432
39000
|
filters.push(["warehouse", "=", input.warehouse]);
|
|
@@ -38807,9 +39375,13 @@ var accountingTools = [
|
|
|
38807
39375
|
},
|
|
38808
39376
|
party_type: {
|
|
38809
39377
|
type: "string",
|
|
38810
|
-
description: "Filter by party type (Customer, Supplier, Employee)"
|
|
39378
|
+
description: "Filter by party type (Customer, Supplier, Employee). Required when 'party' is set, so the party name/ID can be resolved against the right doctype.",
|
|
39379
|
+
enum: ["Customer", "Supplier", "Employee"]
|
|
39380
|
+
},
|
|
39381
|
+
party: {
|
|
39382
|
+
type: "string",
|
|
39383
|
+
description: "Filter by party \u2014 ID or name (e.g. 'CUST-00001' or 'Acme Corp'). Requires 'party_type'."
|
|
38811
39384
|
},
|
|
38812
|
-
party: { type: "string", description: "Filter by party name" },
|
|
38813
39385
|
date_from: {
|
|
38814
39386
|
type: "string",
|
|
38815
39387
|
description: "Start date filter YYYY-MM-DD"
|
|
@@ -38826,7 +39398,20 @@ var accountingTools = [
|
|
|
38826
39398
|
filters.push(["party_type", "=", input.party_type]);
|
|
38827
39399
|
}
|
|
38828
39400
|
if (input.party) {
|
|
38829
|
-
|
|
39401
|
+
if (!input.party_type) {
|
|
39402
|
+
throw new Error(
|
|
39403
|
+
"[erpnext_payment_entry_list] 'party_type' is required when filtering by 'party'"
|
|
39404
|
+
);
|
|
39405
|
+
}
|
|
39406
|
+
filters.push([
|
|
39407
|
+
"party",
|
|
39408
|
+
"=",
|
|
39409
|
+
await resolveDynamicLink(
|
|
39410
|
+
ctx.client,
|
|
39411
|
+
input.party_type,
|
|
39412
|
+
input.party
|
|
39413
|
+
)
|
|
39414
|
+
]);
|
|
38830
39415
|
}
|
|
38831
39416
|
if (input.date_from) {
|
|
38832
39417
|
filters.push(["posting_date", ">=", input.date_from]);
|
|
@@ -39030,7 +39615,10 @@ var hrTools = [
|
|
|
39030
39615
|
type: "object",
|
|
39031
39616
|
properties: {
|
|
39032
39617
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
39033
|
-
employee: {
|
|
39618
|
+
employee: {
|
|
39619
|
+
type: "string",
|
|
39620
|
+
description: "Filter by employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
39621
|
+
},
|
|
39034
39622
|
status: {
|
|
39035
39623
|
type: "string",
|
|
39036
39624
|
description: "Filter by status (Present, Absent, Half Day, On Leave)",
|
|
@@ -39047,7 +39635,11 @@ var hrTools = [
|
|
|
39047
39635
|
const limit = input.limit ?? 20;
|
|
39048
39636
|
const filters = [];
|
|
39049
39637
|
if (input.employee) {
|
|
39050
|
-
filters.push([
|
|
39638
|
+
filters.push([
|
|
39639
|
+
"employee",
|
|
39640
|
+
"=",
|
|
39641
|
+
await resolveEmployee(ctx.client, input.employee)
|
|
39642
|
+
]);
|
|
39051
39643
|
}
|
|
39052
39644
|
if (input.status) {
|
|
39053
39645
|
filters.push(["status", "=", input.status]);
|
|
@@ -39089,7 +39681,10 @@ var hrTools = [
|
|
|
39089
39681
|
type: "object",
|
|
39090
39682
|
properties: {
|
|
39091
39683
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
39092
|
-
employee: {
|
|
39684
|
+
employee: {
|
|
39685
|
+
type: "string",
|
|
39686
|
+
description: "Filter by employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
39687
|
+
},
|
|
39093
39688
|
status: {
|
|
39094
39689
|
type: "string",
|
|
39095
39690
|
description: "Filter by status (Open, Approved, Rejected, Cancelled)",
|
|
@@ -39098,14 +39693,23 @@ var hrTools = [
|
|
|
39098
39693
|
leave_type: {
|
|
39099
39694
|
type: "string",
|
|
39100
39695
|
description: "Filter by leave type (e.g. Sick Leave)"
|
|
39101
|
-
}
|
|
39696
|
+
},
|
|
39697
|
+
date_from: {
|
|
39698
|
+
type: "string",
|
|
39699
|
+
description: "Start date filter YYYY-MM-DD"
|
|
39700
|
+
},
|
|
39701
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
39102
39702
|
}
|
|
39103
39703
|
},
|
|
39104
39704
|
handler: async (input, ctx) => {
|
|
39105
39705
|
const limit = input.limit ?? 20;
|
|
39106
39706
|
const filters = [];
|
|
39107
39707
|
if (input.employee) {
|
|
39108
|
-
filters.push([
|
|
39708
|
+
filters.push([
|
|
39709
|
+
"employee",
|
|
39710
|
+
"=",
|
|
39711
|
+
await resolveEmployee(ctx.client, input.employee)
|
|
39712
|
+
]);
|
|
39109
39713
|
}
|
|
39110
39714
|
if (input.status) {
|
|
39111
39715
|
filters.push(["status", "=", input.status]);
|
|
@@ -39113,6 +39717,12 @@ var hrTools = [
|
|
|
39113
39717
|
if (input.leave_type) {
|
|
39114
39718
|
filters.push(["leave_type", "=", input.leave_type]);
|
|
39115
39719
|
}
|
|
39720
|
+
if (input.date_from) {
|
|
39721
|
+
filters.push(["from_date", ">=", input.date_from]);
|
|
39722
|
+
}
|
|
39723
|
+
if (input.date_to) {
|
|
39724
|
+
filters.push(["to_date", "<=", input.date_to]);
|
|
39725
|
+
}
|
|
39116
39726
|
const docs = await ctx.client.list("Leave Application", {
|
|
39117
39727
|
fields: [
|
|
39118
39728
|
"name",
|
|
@@ -39227,7 +39837,10 @@ var hrTools = [
|
|
|
39227
39837
|
type: "object",
|
|
39228
39838
|
properties: {
|
|
39229
39839
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
39230
|
-
employee: {
|
|
39840
|
+
employee: {
|
|
39841
|
+
type: "string",
|
|
39842
|
+
description: "Filter by employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
39843
|
+
},
|
|
39231
39844
|
status: {
|
|
39232
39845
|
type: "string",
|
|
39233
39846
|
description: "Filter by status (Draft, Submitted, Cancelled)",
|
|
@@ -39247,7 +39860,11 @@ var hrTools = [
|
|
|
39247
39860
|
const limit = input.limit ?? 20;
|
|
39248
39861
|
const filters = [];
|
|
39249
39862
|
if (input.employee) {
|
|
39250
|
-
filters.push([
|
|
39863
|
+
filters.push([
|
|
39864
|
+
"employee",
|
|
39865
|
+
"=",
|
|
39866
|
+
await resolveEmployee(ctx.client, input.employee)
|
|
39867
|
+
]);
|
|
39251
39868
|
}
|
|
39252
39869
|
if (input.status) {
|
|
39253
39870
|
filters.push(["status", "=", input.status]);
|
|
@@ -39321,7 +39938,12 @@ var hrTools = [
|
|
|
39321
39938
|
type: "string",
|
|
39322
39939
|
description: "Filter by status (Draft, Submitted, Cancelled)",
|
|
39323
39940
|
enum: ["Draft", "Submitted", "Cancelled"]
|
|
39324
|
-
}
|
|
39941
|
+
},
|
|
39942
|
+
date_from: {
|
|
39943
|
+
type: "string",
|
|
39944
|
+
description: "Start date filter YYYY-MM-DD"
|
|
39945
|
+
},
|
|
39946
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
39325
39947
|
}
|
|
39326
39948
|
},
|
|
39327
39949
|
handler: async (input, ctx) => {
|
|
@@ -39333,6 +39955,12 @@ var hrTools = [
|
|
|
39333
39955
|
if (input.status) {
|
|
39334
39956
|
filters.push(["status", "=", input.status]);
|
|
39335
39957
|
}
|
|
39958
|
+
if (input.date_from) {
|
|
39959
|
+
filters.push(["posting_date", ">=", input.date_from]);
|
|
39960
|
+
}
|
|
39961
|
+
if (input.date_to) {
|
|
39962
|
+
filters.push(["posting_date", "<=", input.date_to]);
|
|
39963
|
+
}
|
|
39336
39964
|
const docs = await ctx.client.list("Payroll Entry", {
|
|
39337
39965
|
fields: [
|
|
39338
39966
|
"name",
|
|
@@ -39364,7 +39992,10 @@ var hrTools = [
|
|
|
39364
39992
|
type: "object",
|
|
39365
39993
|
properties: {
|
|
39366
39994
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
39367
|
-
employee: {
|
|
39995
|
+
employee: {
|
|
39996
|
+
type: "string",
|
|
39997
|
+
description: "Filter by employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
39998
|
+
},
|
|
39368
39999
|
status: {
|
|
39369
40000
|
type: "string",
|
|
39370
40001
|
description: "Filter by status (Draft, Submitted, Cancelled)",
|
|
@@ -39374,14 +40005,23 @@ var hrTools = [
|
|
|
39374
40005
|
type: "string",
|
|
39375
40006
|
description: "Filter by approval status (Pending, Approved, Rejected)",
|
|
39376
40007
|
enum: ["Pending", "Approved", "Rejected"]
|
|
39377
|
-
}
|
|
40008
|
+
},
|
|
40009
|
+
date_from: {
|
|
40010
|
+
type: "string",
|
|
40011
|
+
description: "Start date filter YYYY-MM-DD"
|
|
40012
|
+
},
|
|
40013
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
39378
40014
|
}
|
|
39379
40015
|
},
|
|
39380
40016
|
handler: async (input, ctx) => {
|
|
39381
40017
|
const limit = input.limit ?? 20;
|
|
39382
40018
|
const filters = [];
|
|
39383
40019
|
if (input.employee) {
|
|
39384
|
-
filters.push([
|
|
40020
|
+
filters.push([
|
|
40021
|
+
"employee",
|
|
40022
|
+
"=",
|
|
40023
|
+
await resolveEmployee(ctx.client, input.employee)
|
|
40024
|
+
]);
|
|
39385
40025
|
}
|
|
39386
40026
|
if (input.status) {
|
|
39387
40027
|
filters.push(["status", "=", input.status]);
|
|
@@ -39389,6 +40029,12 @@ var hrTools = [
|
|
|
39389
40029
|
if (input.approval_status) {
|
|
39390
40030
|
filters.push(["approval_status", "=", input.approval_status]);
|
|
39391
40031
|
}
|
|
40032
|
+
if (input.date_from) {
|
|
40033
|
+
filters.push(["posting_date", ">=", input.date_from]);
|
|
40034
|
+
}
|
|
40035
|
+
if (input.date_to) {
|
|
40036
|
+
filters.push(["posting_date", "<=", input.date_to]);
|
|
40037
|
+
}
|
|
39392
40038
|
const docs = await ctx.client.list("Expense Claim", {
|
|
39393
40039
|
fields: [
|
|
39394
40040
|
"name",
|
|
@@ -39490,7 +40136,7 @@ var hrTools = [
|
|
|
39490
40136
|
properties: {
|
|
39491
40137
|
employee: {
|
|
39492
40138
|
type: "string",
|
|
39493
|
-
description: "Employee ID (e.g. HR-EMP-00001)"
|
|
40139
|
+
description: "Employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
39494
40140
|
}
|
|
39495
40141
|
},
|
|
39496
40142
|
required: ["employee"]
|
|
@@ -39500,7 +40146,11 @@ var hrTools = [
|
|
|
39500
40146
|
throw new Error("[erpnext_leave_balance] 'employee' is required");
|
|
39501
40147
|
}
|
|
39502
40148
|
const filters = [
|
|
39503
|
-
[
|
|
40149
|
+
[
|
|
40150
|
+
"employee",
|
|
40151
|
+
"=",
|
|
40152
|
+
await resolveEmployee(ctx.client, input.employee)
|
|
40153
|
+
],
|
|
39504
40154
|
["docstatus", "=", 1]
|
|
39505
40155
|
];
|
|
39506
40156
|
const docs = await ctx.client.list("Leave Allocation", {
|
|
@@ -39701,7 +40351,12 @@ var projectTools = [
|
|
|
39701
40351
|
description: "Filter by status (Open, Completed, Cancelled)",
|
|
39702
40352
|
enum: ["Open", "Completed", "Cancelled"]
|
|
39703
40353
|
},
|
|
39704
|
-
company: { type: "string", description: "Filter by company" }
|
|
40354
|
+
company: { type: "string", description: "Filter by company" },
|
|
40355
|
+
date_from: {
|
|
40356
|
+
type: "string",
|
|
40357
|
+
description: "Start date filter YYYY-MM-DD"
|
|
40358
|
+
},
|
|
40359
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
39705
40360
|
}
|
|
39706
40361
|
},
|
|
39707
40362
|
handler: async (input, ctx) => {
|
|
@@ -39713,6 +40368,12 @@ var projectTools = [
|
|
|
39713
40368
|
if (input.company) {
|
|
39714
40369
|
filters.push(["company", "=", input.company]);
|
|
39715
40370
|
}
|
|
40371
|
+
if (input.date_from) {
|
|
40372
|
+
filters.push(["expected_start_date", ">=", input.date_from]);
|
|
40373
|
+
}
|
|
40374
|
+
if (input.date_to) {
|
|
40375
|
+
filters.push(["expected_end_date", "<=", input.date_to]);
|
|
40376
|
+
}
|
|
39716
40377
|
const docs = await ctx.client.list("Project", {
|
|
39717
40378
|
fields: [
|
|
39718
40379
|
"name",
|
|
@@ -39775,7 +40436,12 @@ var projectTools = [
|
|
|
39775
40436
|
type: "string",
|
|
39776
40437
|
description: "Filter by priority (Low, Medium, High, Urgent)",
|
|
39777
40438
|
enum: ["Low", "Medium", "High", "Urgent"]
|
|
39778
|
-
}
|
|
40439
|
+
},
|
|
40440
|
+
date_from: {
|
|
40441
|
+
type: "string",
|
|
40442
|
+
description: "Start date filter YYYY-MM-DD"
|
|
40443
|
+
},
|
|
40444
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
39779
40445
|
}
|
|
39780
40446
|
},
|
|
39781
40447
|
handler: async (input, ctx) => {
|
|
@@ -39790,6 +40456,12 @@ var projectTools = [
|
|
|
39790
40456
|
if (input.priority) {
|
|
39791
40457
|
filters.push(["priority", "=", input.priority]);
|
|
39792
40458
|
}
|
|
40459
|
+
if (input.date_from) {
|
|
40460
|
+
filters.push(["exp_start_date", ">=", input.date_from]);
|
|
40461
|
+
}
|
|
40462
|
+
if (input.date_to) {
|
|
40463
|
+
filters.push(["exp_end_date", "<=", input.date_to]);
|
|
40464
|
+
}
|
|
39793
40465
|
const docs = await ctx.client.list("Task", {
|
|
39794
40466
|
fields: [
|
|
39795
40467
|
"name",
|
|
@@ -40032,19 +40704,31 @@ var projectTools = [
|
|
|
40032
40704
|
type: "object",
|
|
40033
40705
|
properties: {
|
|
40034
40706
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40035
|
-
employee: {
|
|
40707
|
+
employee: {
|
|
40708
|
+
type: "string",
|
|
40709
|
+
description: "Filter by employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
40710
|
+
},
|
|
40036
40711
|
project: { type: "string", description: "Filter by project name" },
|
|
40037
40712
|
status: {
|
|
40038
40713
|
type: "string",
|
|
40039
40714
|
description: "Filter by status (Draft, Submitted, Cancelled)"
|
|
40040
|
-
}
|
|
40715
|
+
},
|
|
40716
|
+
date_from: {
|
|
40717
|
+
type: "string",
|
|
40718
|
+
description: "Start date filter YYYY-MM-DD"
|
|
40719
|
+
},
|
|
40720
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
40041
40721
|
}
|
|
40042
40722
|
},
|
|
40043
40723
|
handler: async (input, ctx) => {
|
|
40044
40724
|
const limit = input.limit ?? 20;
|
|
40045
40725
|
const filters = [];
|
|
40046
40726
|
if (input.employee) {
|
|
40047
|
-
filters.push([
|
|
40727
|
+
filters.push([
|
|
40728
|
+
"employee",
|
|
40729
|
+
"=",
|
|
40730
|
+
await resolveEmployee(ctx.client, input.employee)
|
|
40731
|
+
]);
|
|
40048
40732
|
}
|
|
40049
40733
|
if (input.project) {
|
|
40050
40734
|
filters.push(["project", "=", input.project]);
|
|
@@ -40052,6 +40736,12 @@ var projectTools = [
|
|
|
40052
40736
|
if (input.status) {
|
|
40053
40737
|
filters.push(["status", "=", input.status]);
|
|
40054
40738
|
}
|
|
40739
|
+
if (input.date_from) {
|
|
40740
|
+
filters.push(["start_date", ">=", input.date_from]);
|
|
40741
|
+
}
|
|
40742
|
+
if (input.date_to) {
|
|
40743
|
+
filters.push(["end_date", "<=", input.date_to]);
|
|
40744
|
+
}
|
|
40055
40745
|
const docs = await ctx.client.list("Timesheet", {
|
|
40056
40746
|
fields: [
|
|
40057
40747
|
"name",
|
|
@@ -40294,7 +40984,10 @@ var purchasingTools = [
|
|
|
40294
40984
|
type: "object",
|
|
40295
40985
|
properties: {
|
|
40296
40986
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40297
|
-
supplier: {
|
|
40987
|
+
supplier: {
|
|
40988
|
+
type: "string",
|
|
40989
|
+
description: "Filter by supplier ID or name (e.g. 'SUPP-00001' or 'Acme Supplies')"
|
|
40990
|
+
},
|
|
40298
40991
|
status: {
|
|
40299
40992
|
type: "string",
|
|
40300
40993
|
description: "Filter by status (Draft, To Receive and Bill, To Bill, Completed, Cancelled, etc.)"
|
|
@@ -40310,7 +41003,11 @@ var purchasingTools = [
|
|
|
40310
41003
|
const limit = input.limit ?? 20;
|
|
40311
41004
|
const filters = [];
|
|
40312
41005
|
if (input.supplier) {
|
|
40313
|
-
filters.push([
|
|
41006
|
+
filters.push([
|
|
41007
|
+
"supplier",
|
|
41008
|
+
"=",
|
|
41009
|
+
await resolveSupplier(ctx.client, input.supplier)
|
|
41010
|
+
]);
|
|
40314
41011
|
}
|
|
40315
41012
|
if (input.status) {
|
|
40316
41013
|
filters.push(["status", "=", input.status]);
|
|
@@ -40442,7 +41139,10 @@ var purchasingTools = [
|
|
|
40442
41139
|
type: "object",
|
|
40443
41140
|
properties: {
|
|
40444
41141
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40445
|
-
supplier: {
|
|
41142
|
+
supplier: {
|
|
41143
|
+
type: "string",
|
|
41144
|
+
description: "Filter by supplier ID or name (e.g. 'SUPP-00001' or 'Acme Supplies')"
|
|
41145
|
+
},
|
|
40446
41146
|
status: {
|
|
40447
41147
|
type: "string",
|
|
40448
41148
|
description: "Filter by status (Draft, Unpaid, Paid, Overdue, Cancelled, etc.)"
|
|
@@ -40458,7 +41158,11 @@ var purchasingTools = [
|
|
|
40458
41158
|
const limit = input.limit ?? 20;
|
|
40459
41159
|
const filters = [];
|
|
40460
41160
|
if (input.supplier) {
|
|
40461
|
-
filters.push([
|
|
41161
|
+
filters.push([
|
|
41162
|
+
"supplier",
|
|
41163
|
+
"=",
|
|
41164
|
+
await resolveSupplier(ctx.client, input.supplier)
|
|
41165
|
+
]);
|
|
40462
41166
|
}
|
|
40463
41167
|
if (input.status) {
|
|
40464
41168
|
filters.push(["status", "=", input.status]);
|
|
@@ -40528,7 +41232,10 @@ var purchasingTools = [
|
|
|
40528
41232
|
type: "object",
|
|
40529
41233
|
properties: {
|
|
40530
41234
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40531
|
-
supplier: {
|
|
41235
|
+
supplier: {
|
|
41236
|
+
type: "string",
|
|
41237
|
+
description: "Filter by supplier ID or name (e.g. 'SUPP-00001' or 'Acme Supplies')"
|
|
41238
|
+
},
|
|
40532
41239
|
status: {
|
|
40533
41240
|
type: "string",
|
|
40534
41241
|
description: "Filter by status (Draft, To Bill, Completed, Cancelled, etc.)"
|
|
@@ -40544,7 +41251,11 @@ var purchasingTools = [
|
|
|
40544
41251
|
const limit = input.limit ?? 20;
|
|
40545
41252
|
const filters = [];
|
|
40546
41253
|
if (input.supplier) {
|
|
40547
|
-
filters.push([
|
|
41254
|
+
filters.push([
|
|
41255
|
+
"supplier",
|
|
41256
|
+
"=",
|
|
41257
|
+
await resolveSupplier(ctx.client, input.supplier)
|
|
41258
|
+
]);
|
|
40548
41259
|
}
|
|
40549
41260
|
if (input.status) {
|
|
40550
41261
|
filters.push(["status", "=", input.status]);
|
|
@@ -40613,22 +41324,40 @@ var purchasingTools = [
|
|
|
40613
41324
|
type: "object",
|
|
40614
41325
|
properties: {
|
|
40615
41326
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40616
|
-
supplier: {
|
|
41327
|
+
supplier: {
|
|
41328
|
+
type: "string",
|
|
41329
|
+
description: "Filter by supplier ID or name (e.g. 'SUPP-00001' or 'Acme Supplies')"
|
|
41330
|
+
},
|
|
40617
41331
|
status: {
|
|
40618
41332
|
type: "string",
|
|
40619
41333
|
description: "Filter by status (Draft, Submitted, Ordered, Lost, Cancelled)"
|
|
40620
|
-
}
|
|
41334
|
+
},
|
|
41335
|
+
date_from: {
|
|
41336
|
+
type: "string",
|
|
41337
|
+
description: "Start date filter YYYY-MM-DD"
|
|
41338
|
+
},
|
|
41339
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
40621
41340
|
}
|
|
40622
41341
|
},
|
|
40623
41342
|
handler: async (input, ctx) => {
|
|
40624
41343
|
const limit = input.limit ?? 20;
|
|
40625
41344
|
const filters = [];
|
|
40626
41345
|
if (input.supplier) {
|
|
40627
|
-
filters.push([
|
|
41346
|
+
filters.push([
|
|
41347
|
+
"supplier",
|
|
41348
|
+
"=",
|
|
41349
|
+
await resolveSupplier(ctx.client, input.supplier)
|
|
41350
|
+
]);
|
|
40628
41351
|
}
|
|
40629
41352
|
if (input.status) {
|
|
40630
41353
|
filters.push(["status", "=", input.status]);
|
|
40631
41354
|
}
|
|
41355
|
+
if (input.date_from) {
|
|
41356
|
+
filters.push(["transaction_date", ">=", input.date_from]);
|
|
41357
|
+
}
|
|
41358
|
+
if (input.date_to) {
|
|
41359
|
+
filters.push(["transaction_date", "<=", input.date_to]);
|
|
41360
|
+
}
|
|
40632
41361
|
const docs = await ctx.client.list("Supplier Quotation", {
|
|
40633
41362
|
fields: [
|
|
40634
41363
|
"name",
|
|
@@ -40664,7 +41393,10 @@ var deliveryTools = [
|
|
|
40664
41393
|
type: "object",
|
|
40665
41394
|
properties: {
|
|
40666
41395
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40667
|
-
customer: {
|
|
41396
|
+
customer: {
|
|
41397
|
+
type: "string",
|
|
41398
|
+
description: "Filter by customer ID or name (e.g. 'CUST-00001' or 'Acme Corp')"
|
|
41399
|
+
},
|
|
40668
41400
|
status: {
|
|
40669
41401
|
type: "string",
|
|
40670
41402
|
description: "Filter by status (Draft, To Bill, Completed, Cancelled, etc.)"
|
|
@@ -40680,7 +41412,11 @@ var deliveryTools = [
|
|
|
40680
41412
|
const limit = input.limit ?? 20;
|
|
40681
41413
|
const filters = [];
|
|
40682
41414
|
if (input.customer) {
|
|
40683
|
-
filters.push([
|
|
41415
|
+
filters.push([
|
|
41416
|
+
"customer",
|
|
41417
|
+
"=",
|
|
41418
|
+
await resolveCustomer(ctx.client, input.customer)
|
|
41419
|
+
]);
|
|
40684
41420
|
}
|
|
40685
41421
|
if (input.status) {
|
|
40686
41422
|
filters.push(["status", "=", input.status]);
|
|
@@ -40899,7 +41635,7 @@ var manufacturingTools = [
|
|
|
40899
41635
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40900
41636
|
item: {
|
|
40901
41637
|
type: "string",
|
|
40902
|
-
description: "Filter by finished goods item code"
|
|
41638
|
+
description: "Filter by finished goods item code or name (e.g. 'ITEM-001' or 'Widget A')"
|
|
40903
41639
|
},
|
|
40904
41640
|
is_active: {
|
|
40905
41641
|
type: "boolean",
|
|
@@ -40915,7 +41651,11 @@ var manufacturingTools = [
|
|
|
40915
41651
|
const limit = input.limit ?? 20;
|
|
40916
41652
|
const filters = [];
|
|
40917
41653
|
if (input.item) {
|
|
40918
|
-
filters.push([
|
|
41654
|
+
filters.push([
|
|
41655
|
+
"item",
|
|
41656
|
+
"=",
|
|
41657
|
+
await resolveItem(ctx.client, input.item)
|
|
41658
|
+
]);
|
|
40919
41659
|
}
|
|
40920
41660
|
if (input.is_active !== void 0) {
|
|
40921
41661
|
filters.push(["is_active", "=", input.is_active ? 1 : 0]);
|
|
@@ -40983,7 +41723,7 @@ var manufacturingTools = [
|
|
|
40983
41723
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
40984
41724
|
production_item: {
|
|
40985
41725
|
type: "string",
|
|
40986
|
-
description: "Filter by item being produced"
|
|
41726
|
+
description: "Filter by item being produced \u2014 item code or name (e.g. 'ITEM-001' or 'Widget A')"
|
|
40987
41727
|
},
|
|
40988
41728
|
status: {
|
|
40989
41729
|
type: "string",
|
|
@@ -41003,7 +41743,11 @@ var manufacturingTools = [
|
|
|
41003
41743
|
const limit = input.limit ?? 20;
|
|
41004
41744
|
const filters = [];
|
|
41005
41745
|
if (input.production_item) {
|
|
41006
|
-
filters.push([
|
|
41746
|
+
filters.push([
|
|
41747
|
+
"production_item",
|
|
41748
|
+
"=",
|
|
41749
|
+
await resolveItem(ctx.client, input.production_item)
|
|
41750
|
+
]);
|
|
41007
41751
|
}
|
|
41008
41752
|
if (input.status) {
|
|
41009
41753
|
filters.push(["status", "=", input.status]);
|
|
@@ -41327,9 +42071,14 @@ var crmTools = [
|
|
|
41327
42071
|
type: "string",
|
|
41328
42072
|
description: "Filter by assigned sales rep (user)"
|
|
41329
42073
|
},
|
|
42074
|
+
opportunity_from: {
|
|
42075
|
+
type: "string",
|
|
42076
|
+
description: "Party type: Customer or Lead. Required when 'party_name' is set, so the party name/ID can be resolved against the right doctype.",
|
|
42077
|
+
enum: ["Customer", "Lead"]
|
|
42078
|
+
},
|
|
41330
42079
|
party_name: {
|
|
41331
42080
|
type: "string",
|
|
41332
|
-
description: "Filter by
|
|
42081
|
+
description: "Filter by party \u2014 ID or name (e.g. customer/lead name). Requires 'opportunity_from'."
|
|
41333
42082
|
}
|
|
41334
42083
|
}
|
|
41335
42084
|
},
|
|
@@ -41347,7 +42096,20 @@ var crmTools = [
|
|
|
41347
42096
|
]);
|
|
41348
42097
|
}
|
|
41349
42098
|
if (input.party_name) {
|
|
41350
|
-
|
|
42099
|
+
if (!input.opportunity_from) {
|
|
42100
|
+
throw new Error(
|
|
42101
|
+
"[erpnext_opportunity_list] 'opportunity_from' is required when filtering by 'party_name'"
|
|
42102
|
+
);
|
|
42103
|
+
}
|
|
42104
|
+
filters.push([
|
|
42105
|
+
"party_name",
|
|
42106
|
+
"=",
|
|
42107
|
+
await resolveDynamicLink(
|
|
42108
|
+
ctx.client,
|
|
42109
|
+
input.opportunity_from,
|
|
42110
|
+
input.party_name
|
|
42111
|
+
)
|
|
42112
|
+
]);
|
|
41351
42113
|
}
|
|
41352
42114
|
const docs = await ctx.client.list("Opportunity", {
|
|
41353
42115
|
fields: [
|
|
@@ -41475,7 +42237,12 @@ var crmTools = [
|
|
|
41475
42237
|
campaign_type: {
|
|
41476
42238
|
type: "string",
|
|
41477
42239
|
description: "Filter by campaign type"
|
|
41478
|
-
}
|
|
42240
|
+
},
|
|
42241
|
+
date_from: {
|
|
42242
|
+
type: "string",
|
|
42243
|
+
description: "Start date filter YYYY-MM-DD"
|
|
42244
|
+
},
|
|
42245
|
+
date_to: { type: "string", description: "End date filter YYYY-MM-DD" }
|
|
41479
42246
|
}
|
|
41480
42247
|
},
|
|
41481
42248
|
handler: async (input, ctx) => {
|
|
@@ -41484,6 +42251,12 @@ var crmTools = [
|
|
|
41484
42251
|
if (input.campaign_type) {
|
|
41485
42252
|
filters.push(["campaign_type", "=", input.campaign_type]);
|
|
41486
42253
|
}
|
|
42254
|
+
if (input.date_from) {
|
|
42255
|
+
filters.push(["start_date", ">=", input.date_from]);
|
|
42256
|
+
}
|
|
42257
|
+
if (input.date_to) {
|
|
42258
|
+
filters.push(["end_date", "<=", input.date_to]);
|
|
42259
|
+
}
|
|
41487
42260
|
const docs = await ctx.client.list("Campaign", {
|
|
41488
42261
|
fields: [
|
|
41489
42262
|
"name",
|
|
@@ -41531,7 +42304,15 @@ var assetsTools = [
|
|
|
41531
42304
|
location: { type: "string", description: "Filter by location" },
|
|
41532
42305
|
custodian: {
|
|
41533
42306
|
type: "string",
|
|
41534
|
-
description: "Filter by custodian (
|
|
42307
|
+
description: "Filter by custodian \u2014 employee ID or name (e.g. 'HR-EMP-00001' or 'John Doe')"
|
|
42308
|
+
},
|
|
42309
|
+
date_from: {
|
|
42310
|
+
type: "string",
|
|
42311
|
+
description: "Purchase date start filter YYYY-MM-DD"
|
|
42312
|
+
},
|
|
42313
|
+
date_to: {
|
|
42314
|
+
type: "string",
|
|
42315
|
+
description: "Purchase date end filter YYYY-MM-DD"
|
|
41535
42316
|
}
|
|
41536
42317
|
}
|
|
41537
42318
|
},
|
|
@@ -41548,7 +42329,17 @@ var assetsTools = [
|
|
|
41548
42329
|
filters.push(["location", "=", input.location]);
|
|
41549
42330
|
}
|
|
41550
42331
|
if (input.custodian) {
|
|
41551
|
-
filters.push([
|
|
42332
|
+
filters.push([
|
|
42333
|
+
"custodian",
|
|
42334
|
+
"=",
|
|
42335
|
+
await resolveEmployee(ctx.client, input.custodian)
|
|
42336
|
+
]);
|
|
42337
|
+
}
|
|
42338
|
+
if (input.date_from) {
|
|
42339
|
+
filters.push(["purchase_date", ">=", input.date_from]);
|
|
42340
|
+
}
|
|
42341
|
+
if (input.date_to) {
|
|
42342
|
+
filters.push(["purchase_date", "<=", input.date_to]);
|
|
41552
42343
|
}
|
|
41553
42344
|
const docs = await ctx.client.list("Asset", {
|
|
41554
42345
|
fields: [
|
|
@@ -42002,11 +42793,13 @@ var operationsTools = [
|
|
|
42002
42793
|
}
|
|
42003
42794
|
const doc = await ctx.client.get(
|
|
42004
42795
|
input.doctype,
|
|
42005
|
-
input.name
|
|
42796
|
+
input.name,
|
|
42797
|
+
{ skipCache: true }
|
|
42006
42798
|
);
|
|
42007
42799
|
const result = await ctx.client.callMethod("frappe.client.submit", {
|
|
42008
42800
|
doc: { ...doc, doctype: input.doctype }
|
|
42009
42801
|
});
|
|
42802
|
+
ctx.client.invalidate(input.doctype, input.name);
|
|
42010
42803
|
return {
|
|
42011
42804
|
data: result,
|
|
42012
42805
|
message: `${input.doctype} ${input.name} submitted successfully`,
|
|
@@ -42046,6 +42839,7 @@ var operationsTools = [
|
|
|
42046
42839
|
doctype: input.doctype,
|
|
42047
42840
|
name: input.name
|
|
42048
42841
|
});
|
|
42842
|
+
ctx.client.invalidate(input.doctype, input.name);
|
|
42049
42843
|
return {
|
|
42050
42844
|
data: result,
|
|
42051
42845
|
message: `${input.doctype} ${input.name} cancelled successfully`,
|
|
@@ -44414,628 +45208,334 @@ var ISSUE_ALLOWED_TRANSITIONS = [
|
|
|
44414
45208
|
{ fromColumn: "resolved", toColumn: "closed", allowed: true, label: "Close" },
|
|
44415
45209
|
{ fromColumn: "closed", toColumn: "open", allowed: true, label: "Reopen" },
|
|
44416
45210
|
{ fromColumn: "closed", toColumn: "replied", allowed: true, label: "Reply" }
|
|
44417
|
-
];
|
|
44418
|
-
function columnIdForIssueStatus(status) {
|
|
44419
|
-
return COLUMN_BY_STATUS3.get(String(status ?? "Open")) ?? "open";
|
|
44420
|
-
}
|
|
44421
|
-
function buildIssueCard(row) {
|
|
44422
|
-
const status = String(row.status ?? "Open");
|
|
44423
|
-
const columnId = columnIdForIssueStatus(status);
|
|
44424
|
-
const priority = typeof row.priority === "string" ? row.priority : void 0;
|
|
44425
|
-
let subtitle;
|
|
44426
|
-
if (typeof row.customer === "string" && row.customer.length > 0) {
|
|
44427
|
-
subtitle = row.customer;
|
|
44428
|
-
} else if (typeof row.raised_by === "string" && row.raised_by.length > 0) {
|
|
44429
|
-
subtitle = row.raised_by;
|
|
44430
|
-
}
|
|
44431
|
-
const badges = [];
|
|
44432
|
-
if (priority) badges.push({ label: priority, tone: priorityTone(priority) });
|
|
44433
|
-
const slaDate = row.resolution_by;
|
|
44434
|
-
if (isDateOverdue(slaDate) && columnId !== "resolved" && columnId !== "closed") {
|
|
44435
|
-
badges.push({ label: "SLA breach", tone: "error" });
|
|
44436
|
-
}
|
|
44437
|
-
const metrics = [];
|
|
44438
|
-
if (typeof row.raised_by === "string" && row.raised_by.length > 0) {
|
|
44439
|
-
metrics.push({ label: "Raised By", value: row.raised_by });
|
|
44440
|
-
}
|
|
44441
|
-
const slaDisplay = formatShortDate(slaDate);
|
|
44442
|
-
if (slaDisplay) metrics.push({ label: "SLA", value: slaDisplay });
|
|
44443
|
-
const openedDisplay = formatShortDate(row.opening_date);
|
|
44444
|
-
if (openedDisplay) metrics.push({ label: "Opened", value: openedDisplay });
|
|
44445
|
-
const resolvedDisplay = formatShortDate(row.resolution_date);
|
|
44446
|
-
if (resolvedDisplay) {
|
|
44447
|
-
metrics.push({ label: "Resolved", value: resolvedDisplay });
|
|
44448
|
-
}
|
|
44449
|
-
const assignee = parseFirstAssignee(row._assign);
|
|
44450
|
-
return {
|
|
44451
|
-
id: String(row.name ?? ""),
|
|
44452
|
-
title: String(row.subject ?? row.name ?? "Untitled issue"),
|
|
44453
|
-
subtitle,
|
|
44454
|
-
columnId,
|
|
44455
|
-
accent: ISSUE_COLUMNS.find((column) => column.id === columnId)?.color,
|
|
44456
|
-
badges,
|
|
44457
|
-
metrics,
|
|
44458
|
-
dueDate: typeof slaDate === "string" ? slaDate : void 0,
|
|
44459
|
-
assignee
|
|
44460
|
-
};
|
|
44461
|
-
}
|
|
44462
|
-
var issueKanbanAdapter = {
|
|
44463
|
-
doctype: "Issue",
|
|
44464
|
-
getColumns() {
|
|
44465
|
-
return ISSUE_COLUMNS.map((column) => ({
|
|
44466
|
-
id: column.id,
|
|
44467
|
-
label: column.label,
|
|
44468
|
-
color: column.color,
|
|
44469
|
-
count: 0
|
|
44470
|
-
}));
|
|
44471
|
-
},
|
|
44472
|
-
getAllowedTransitions() {
|
|
44473
|
-
return ISSUE_ALLOWED_TRANSITIONS;
|
|
44474
|
-
},
|
|
44475
|
-
getListFields() {
|
|
44476
|
-
return ISSUE_LIST_FIELDS;
|
|
44477
|
-
},
|
|
44478
|
-
buildListFilters(input) {
|
|
44479
|
-
const filters = [];
|
|
44480
|
-
if (typeof input.status === "string" && input.status.length > 0) {
|
|
44481
|
-
filters.push(["status", "=", input.status]);
|
|
44482
|
-
}
|
|
44483
|
-
if (typeof input.priority === "string" && input.priority.length > 0) {
|
|
44484
|
-
filters.push(["priority", "=", input.priority]);
|
|
44485
|
-
}
|
|
44486
|
-
if (typeof input.customer === "string" && input.customer.length > 0) {
|
|
44487
|
-
filters.push(["customer", "=", input.customer]);
|
|
44488
|
-
}
|
|
44489
|
-
if (typeof input.raised_by === "string" && input.raised_by.length > 0) {
|
|
44490
|
-
filters.push(["raised_by", "=", input.raised_by]);
|
|
44491
|
-
}
|
|
44492
|
-
return filters;
|
|
44493
|
-
},
|
|
44494
|
-
buildCards(rows) {
|
|
44495
|
-
return rows.map(buildIssueCard);
|
|
44496
|
-
},
|
|
44497
|
-
validateTransition(move) {
|
|
44498
|
-
const match2 = ISSUE_ALLOWED_TRANSITIONS.find(
|
|
44499
|
-
(transition) => transition.fromColumn === move.fromColumn && transition.toColumn === move.toColumn && transition.allowed
|
|
44500
|
-
);
|
|
44501
|
-
if (!match2) {
|
|
44502
|
-
return { allowed: false, reason: "Issue transition is not allowed" };
|
|
44503
|
-
}
|
|
44504
|
-
return { allowed: true };
|
|
44505
|
-
},
|
|
44506
|
-
async executeMove(move, ctx) {
|
|
44507
|
-
const currentIssue = await ctx.client.get("Issue", move.cardId);
|
|
44508
|
-
const serverColumn = columnIdForIssueStatus(currentIssue.status);
|
|
44509
|
-
if (serverColumn !== move.fromColumn) {
|
|
44510
|
-
return {
|
|
44511
|
-
ok: false,
|
|
44512
|
-
cardId: move.cardId,
|
|
44513
|
-
fromColumn: serverColumn,
|
|
44514
|
-
toColumn: move.toColumn,
|
|
44515
|
-
errorMessage: `Issue moved on the server from ${move.fromColumn} to ${serverColumn}. Refresh the board and try again.`
|
|
44516
|
-
};
|
|
44517
|
-
}
|
|
44518
|
-
const validation = this.validateTransition(move);
|
|
44519
|
-
if (!validation.allowed) {
|
|
44520
|
-
return {
|
|
44521
|
-
ok: false,
|
|
44522
|
-
cardId: move.cardId,
|
|
44523
|
-
fromColumn: move.fromColumn,
|
|
44524
|
-
toColumn: move.toColumn,
|
|
44525
|
-
errorMessage: validation.reason
|
|
44526
|
-
};
|
|
44527
|
-
}
|
|
44528
|
-
const status = STATUS_BY_COLUMN3.get(move.toColumn);
|
|
44529
|
-
if (!status) {
|
|
44530
|
-
return {
|
|
44531
|
-
ok: false,
|
|
44532
|
-
cardId: move.cardId,
|
|
44533
|
-
fromColumn: move.fromColumn,
|
|
44534
|
-
toColumn: move.toColumn,
|
|
44535
|
-
errorMessage: "Unknown Issue column"
|
|
44536
|
-
};
|
|
44537
|
-
}
|
|
44538
|
-
const serverIssue = await ctx.client.update("Issue", move.cardId, {
|
|
44539
|
-
status
|
|
44540
|
-
});
|
|
44541
|
-
return {
|
|
44542
|
-
ok: true,
|
|
44543
|
-
cardId: move.cardId,
|
|
44544
|
-
fromColumn: serverColumn,
|
|
44545
|
-
toColumn: move.toColumn,
|
|
44546
|
-
serverCard: buildIssueCard(serverIssue)
|
|
44547
|
-
};
|
|
44548
|
-
}
|
|
44549
|
-
};
|
|
44550
|
-
|
|
44551
|
-
// src/tools/kanban.ts
|
|
44552
|
-
var ADAPTERS = {
|
|
44553
|
-
task: taskKanbanAdapter,
|
|
44554
|
-
opportunity: opportunityKanbanAdapter,
|
|
44555
|
-
issue: issueKanbanAdapter
|
|
44556
|
-
};
|
|
44557
|
-
function getAdapter(doctype) {
|
|
44558
|
-
const definition = getKanbanBoardDefinition(doctype);
|
|
44559
|
-
if (!definition) {
|
|
44560
|
-
throw new Error(`[erpnext_kanban] Unsupported kanban doctype: ${doctype}`);
|
|
44561
|
-
}
|
|
44562
|
-
const adapter = ADAPTERS[definition.adapterKey];
|
|
44563
|
-
if (!adapter) {
|
|
44564
|
-
throw new Error(`[erpnext_kanban] No adapter registered for ${doctype}`);
|
|
44565
|
-
}
|
|
44566
|
-
return { definition, adapter };
|
|
44567
|
-
}
|
|
44568
|
-
function withColumnCounts(columns, cards) {
|
|
44569
|
-
const counts = /* @__PURE__ */ new Map();
|
|
44570
|
-
for (const card of cards) {
|
|
44571
|
-
counts.set(card.columnId, (counts.get(card.columnId) ?? 0) + 1);
|
|
44572
|
-
}
|
|
44573
|
-
return columns.map((column) => ({
|
|
44574
|
-
...column,
|
|
44575
|
-
count: counts.get(column.id) ?? 0
|
|
44576
|
-
}));
|
|
44577
|
-
}
|
|
44578
|
-
var kanbanTools = [
|
|
44579
|
-
{
|
|
44580
|
-
name: "erpnext_kanban_get_board",
|
|
44581
|
-
annotations: { readOnlyHint: true },
|
|
44582
|
-
category: "kanban",
|
|
44583
|
-
_meta: KANBAN_META,
|
|
44584
|
-
description: "Get a normalized kanban board for a supported ERPNext DocType. Supports Task, Opportunity, and Issue, with pagination and MCP App metadata.",
|
|
44585
|
-
inputSchema: {
|
|
44586
|
-
type: "object",
|
|
44587
|
-
properties: {
|
|
44588
|
-
doctype: {
|
|
44589
|
-
type: "string",
|
|
44590
|
-
description: "Kanban-enabled ERPNext DocType",
|
|
44591
|
-
enum: ["Task", "Opportunity", "Issue"]
|
|
44592
|
-
},
|
|
44593
|
-
limit: { type: "number", description: "Page size (default 50)" },
|
|
44594
|
-
offset: {
|
|
44595
|
-
type: "number",
|
|
44596
|
-
description: "Pagination offset (default 0)"
|
|
44597
|
-
},
|
|
44598
|
-
project: {
|
|
44599
|
-
type: "string",
|
|
44600
|
-
description: "Optional Task project filter"
|
|
44601
|
-
},
|
|
44602
|
-
priority: {
|
|
44603
|
-
type: "string",
|
|
44604
|
-
description: "Optional Task priority filter",
|
|
44605
|
-
enum: ["Low", "Medium", "High", "Urgent"]
|
|
44606
|
-
},
|
|
44607
|
-
status: {
|
|
44608
|
-
type: "string",
|
|
44609
|
-
description: "Optional Opportunity status filter",
|
|
44610
|
-
enum: ["Open", "Replied", "Quotation", "Converted", "Closed", "Lost"]
|
|
44611
|
-
},
|
|
44612
|
-
opportunity_owner: {
|
|
44613
|
-
type: "string",
|
|
44614
|
-
description: "Optional Opportunity owner filter"
|
|
44615
|
-
},
|
|
44616
|
-
party_name: {
|
|
44617
|
-
type: "string",
|
|
44618
|
-
description: "Optional Opportunity party filter"
|
|
44619
|
-
},
|
|
44620
|
-
customer: {
|
|
44621
|
-
type: "string",
|
|
44622
|
-
description: "Optional Issue customer filter"
|
|
44623
|
-
},
|
|
44624
|
-
raised_by: {
|
|
44625
|
-
type: "string",
|
|
44626
|
-
description: "Optional Issue reporter email filter"
|
|
44627
|
-
}
|
|
44628
|
-
},
|
|
44629
|
-
required: ["doctype"]
|
|
44630
|
-
},
|
|
44631
|
-
handler: async (input, ctx) => {
|
|
44632
|
-
const doctype = String(input.doctype ?? "");
|
|
44633
|
-
const { definition, adapter } = getAdapter(doctype);
|
|
44634
|
-
const limit = Math.max(1, Number(input.limit ?? 50));
|
|
44635
|
-
const offset = Math.max(0, Number(input.offset ?? 0));
|
|
44636
|
-
const rows = await ctx.client.list(doctype, {
|
|
44637
|
-
fields: adapter.getListFields(),
|
|
44638
|
-
filters: adapter.buildListFilters(input),
|
|
44639
|
-
limit: limit + 1,
|
|
44640
|
-
limit_start: offset,
|
|
44641
|
-
order_by: "modified desc"
|
|
44642
|
-
});
|
|
44643
|
-
const hasMore = rows.length > limit;
|
|
44644
|
-
const visibleRows = rows.slice(0, limit);
|
|
44645
|
-
const cards = adapter.buildCards(visibleRows);
|
|
44646
|
-
const board = {
|
|
44647
|
-
boardId: `${doctype.toLowerCase()}-board`,
|
|
44648
|
-
title: definition.title,
|
|
44649
|
-
doctype,
|
|
44650
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44651
|
-
moveToolName: definition.moveToolName,
|
|
44652
|
-
refreshArguments: { ...input, doctype, limit, offset },
|
|
44653
|
-
columns: withColumnCounts(adapter.getColumns(), cards),
|
|
44654
|
-
cards,
|
|
44655
|
-
allowedTransitions: adapter.getAllowedTransitions(),
|
|
44656
|
-
capabilities: { canMoveCards: true },
|
|
44657
|
-
pagination: {
|
|
44658
|
-
limit,
|
|
44659
|
-
offset,
|
|
44660
|
-
loadedCount: offset + visibleRows.length,
|
|
44661
|
-
hasMore
|
|
44662
|
-
}
|
|
44663
|
-
};
|
|
44664
|
-
return {
|
|
44665
|
-
...board,
|
|
44666
|
-
_meta: KANBAN_META
|
|
44667
|
-
};
|
|
44668
|
-
}
|
|
44669
|
-
},
|
|
44670
|
-
{
|
|
44671
|
-
name: "erpnext_kanban_move_card",
|
|
44672
|
-
category: "kanban",
|
|
44673
|
-
description: "Move a kanban card for a supported ERPNext DocType. Returns structured success or business error details for MCP App reconciliation.",
|
|
44674
|
-
inputSchema: {
|
|
44675
|
-
type: "object",
|
|
44676
|
-
properties: {
|
|
44677
|
-
doctype: {
|
|
44678
|
-
type: "string",
|
|
44679
|
-
description: "Kanban-enabled ERPNext DocType",
|
|
44680
|
-
enum: ["Task", "Opportunity", "Issue"]
|
|
44681
|
-
},
|
|
44682
|
-
card_id: { type: "string", description: "Card/document identifier" },
|
|
44683
|
-
from_column: {
|
|
44684
|
-
type: "string",
|
|
44685
|
-
description: "Source column identifier"
|
|
44686
|
-
},
|
|
44687
|
-
to_column: {
|
|
44688
|
-
type: "string",
|
|
44689
|
-
description: "Destination column identifier"
|
|
44690
|
-
}
|
|
44691
|
-
},
|
|
44692
|
-
required: ["doctype", "card_id", "from_column", "to_column"]
|
|
44693
|
-
},
|
|
44694
|
-
handler: async (input, ctx) => {
|
|
44695
|
-
const doctype = String(input.doctype ?? "");
|
|
44696
|
-
const { adapter } = getAdapter(doctype);
|
|
44697
|
-
return await adapter.executeMove({
|
|
44698
|
-
doctype,
|
|
44699
|
-
cardId: String(input.card_id ?? ""),
|
|
44700
|
-
fromColumn: String(input.from_column ?? ""),
|
|
44701
|
-
toColumn: String(input.to_column ?? "")
|
|
44702
|
-
}, ctx);
|
|
44703
|
-
}
|
|
44704
|
-
}
|
|
44705
|
-
];
|
|
44706
|
-
|
|
44707
|
-
// src/tools/mod.ts
|
|
44708
|
-
var toolsByCategory = {
|
|
44709
|
-
sales: salesTools,
|
|
44710
|
-
inventory: inventoryTools,
|
|
44711
|
-
accounting: accountingTools,
|
|
44712
|
-
hr: hrTools,
|
|
44713
|
-
project: projectTools,
|
|
44714
|
-
purchasing: purchasingTools,
|
|
44715
|
-
delivery: deliveryTools,
|
|
44716
|
-
manufacturing: manufacturingTools,
|
|
44717
|
-
crm: crmTools,
|
|
44718
|
-
assets: assetsTools,
|
|
44719
|
-
operations: operationsTools,
|
|
44720
|
-
setup: setupTools,
|
|
44721
|
-
analytics: analyticsTools,
|
|
44722
|
-
kanban: kanbanTools
|
|
44723
|
-
};
|
|
44724
|
-
var allTools = [
|
|
44725
|
-
...salesTools,
|
|
44726
|
-
...inventoryTools,
|
|
44727
|
-
...accountingTools,
|
|
44728
|
-
...hrTools,
|
|
44729
|
-
...projectTools,
|
|
44730
|
-
...purchasingTools,
|
|
44731
|
-
...deliveryTools,
|
|
44732
|
-
...manufacturingTools,
|
|
44733
|
-
...crmTools,
|
|
44734
|
-
...assetsTools,
|
|
44735
|
-
...operationsTools,
|
|
44736
|
-
...setupTools,
|
|
44737
|
-
...analyticsTools,
|
|
44738
|
-
...kanbanTools
|
|
44739
|
-
];
|
|
44740
|
-
function getToolsByCategory(category) {
|
|
44741
|
-
return toolsByCategory[category] ?? [];
|
|
44742
|
-
}
|
|
44743
|
-
|
|
44744
|
-
// src/runtime.ts
|
|
44745
|
-
var isDeno2 = typeof globalThis.Deno?.version?.deno === "string";
|
|
44746
|
-
var impl2 = isDeno2 ? await Promise.resolve().then(() => (init_runtime_deno2(), runtime_deno_exports2)) : await Promise.resolve().then(() => (init_runtime_node2(), runtime_node_exports2));
|
|
44747
|
-
var env6 = impl2.env;
|
|
44748
|
-
var readTextFile6 = impl2.readTextFile;
|
|
44749
|
-
var statSync3 = impl2.statSync;
|
|
44750
|
-
var readDirSync3 = impl2.readDirSync;
|
|
44751
|
-
var getArgs3 = impl2.getArgs;
|
|
44752
|
-
var exit3 = impl2.exit;
|
|
44753
|
-
var onSignal3 = impl2.onSignal;
|
|
44754
|
-
|
|
44755
|
-
// src/api/frappe-client.ts
|
|
44756
|
-
var DEFAULT_RETRY_STATUSES = [408, 429, 502, 503, 504];
|
|
44757
|
-
var DEFAULT_RETRY_METHODS = ["GET"];
|
|
44758
|
-
var FrappeAPIError = class extends Error {
|
|
44759
|
-
/**
|
|
44760
|
-
* @param message - Human-readable error description
|
|
44761
|
-
* @param status - HTTP status code (0 for network errors, 408 for timeouts)
|
|
44762
|
-
* @param body - Raw response body (parsed JSON object or plain text string)
|
|
44763
|
-
* @param retryAfterMs - When the server sent a `Retry-After` header on a
|
|
44764
|
-
* retryable status (typically 429), the parsed delay
|
|
44765
|
-
* in ms. Used by the retry loop; absent otherwise.
|
|
44766
|
-
*/
|
|
44767
|
-
constructor(message2, status, body, retryAfterMs) {
|
|
44768
|
-
super(`[FrappeClient] ${message2} (HTTP ${status})`);
|
|
44769
|
-
this.status = status;
|
|
44770
|
-
this.body = body;
|
|
44771
|
-
this.retryAfterMs = retryAfterMs;
|
|
44772
|
-
this.name = "FrappeAPIError";
|
|
44773
|
-
}
|
|
44774
|
-
};
|
|
44775
|
-
function parseRetryAfter(raw2) {
|
|
44776
|
-
if (!raw2) return void 0;
|
|
44777
|
-
const trimmed = raw2.trim();
|
|
44778
|
-
const seconds = Number(trimmed);
|
|
44779
|
-
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
44780
|
-
return Math.round(seconds * 1e3);
|
|
44781
|
-
}
|
|
44782
|
-
const date3 = Date.parse(trimmed);
|
|
44783
|
-
if (Number.isFinite(date3)) {
|
|
44784
|
-
return Math.max(0, date3 - Date.now());
|
|
44785
|
-
}
|
|
44786
|
-
return void 0;
|
|
44787
|
-
}
|
|
44788
|
-
function extractServerMessages(raw2) {
|
|
44789
|
-
if (!raw2 || typeof raw2 !== "string") return void 0;
|
|
44790
|
-
try {
|
|
44791
|
-
const outer = JSON.parse(raw2);
|
|
44792
|
-
if (!Array.isArray(outer)) return void 0;
|
|
44793
|
-
const msgs = [];
|
|
44794
|
-
for (const item of outer) {
|
|
44795
|
-
try {
|
|
44796
|
-
const inner = JSON.parse(item);
|
|
44797
|
-
if (typeof inner === "object" && inner?.message) {
|
|
44798
|
-
msgs.push(inner.message);
|
|
44799
|
-
} else if (typeof inner === "string") {
|
|
44800
|
-
msgs.push(inner);
|
|
44801
|
-
}
|
|
44802
|
-
} catch {
|
|
44803
|
-
if (typeof item === "string") msgs.push(item);
|
|
44804
|
-
}
|
|
44805
|
-
}
|
|
44806
|
-
return msgs.length > 0 ? msgs.join("; ") : void 0;
|
|
44807
|
-
} catch {
|
|
44808
|
-
return void 0;
|
|
44809
|
-
}
|
|
44810
|
-
}
|
|
44811
|
-
var FrappeClient = class {
|
|
44812
|
-
baseUrl;
|
|
44813
|
-
authHeader;
|
|
44814
|
-
timeoutMs;
|
|
44815
|
-
retries;
|
|
44816
|
-
retryStatuses;
|
|
44817
|
-
retryBackoffMs;
|
|
44818
|
-
retryMethods;
|
|
44819
|
-
constructor(config2) {
|
|
44820
|
-
this.baseUrl = config2.baseUrl.replace(/\/$/, "");
|
|
44821
|
-
this.authHeader = `token ${config2.apiKey}:${config2.apiSecret}`;
|
|
44822
|
-
this.timeoutMs = config2.timeoutMs ?? 3e4;
|
|
44823
|
-
this.retries = config2.retries ?? 3;
|
|
44824
|
-
this.retryStatuses = config2.retryStatuses ?? DEFAULT_RETRY_STATUSES;
|
|
44825
|
-
this.retryBackoffMs = config2.retryBackoffMs ?? 200;
|
|
44826
|
-
this.retryMethods = config2.retryMethods ?? DEFAULT_RETRY_METHODS;
|
|
44827
|
-
}
|
|
44828
|
-
// ── Private HTTP helpers ────────────────────────────────────────────────────
|
|
44829
|
-
buildHeaders() {
|
|
44830
|
-
return {
|
|
44831
|
-
"Authorization": this.authHeader,
|
|
44832
|
-
"Accept": "application/json",
|
|
44833
|
-
"Content-Type": "application/json"
|
|
44834
|
-
};
|
|
45211
|
+
];
|
|
45212
|
+
function columnIdForIssueStatus(status) {
|
|
45213
|
+
return COLUMN_BY_STATUS3.get(String(status ?? "Open")) ?? "open";
|
|
45214
|
+
}
|
|
45215
|
+
function buildIssueCard(row) {
|
|
45216
|
+
const status = String(row.status ?? "Open");
|
|
45217
|
+
const columnId = columnIdForIssueStatus(status);
|
|
45218
|
+
const priority = typeof row.priority === "string" ? row.priority : void 0;
|
|
45219
|
+
let subtitle;
|
|
45220
|
+
if (typeof row.customer === "string" && row.customer.length > 0) {
|
|
45221
|
+
subtitle = row.customer;
|
|
45222
|
+
} else if (typeof row.raised_by === "string" && row.raised_by.length > 0) {
|
|
45223
|
+
subtitle = row.raised_by;
|
|
44835
45224
|
}
|
|
44836
|
-
|
|
44837
|
-
|
|
44838
|
-
|
|
44839
|
-
|
|
44840
|
-
|
|
44841
|
-
isRetryable(err, method) {
|
|
44842
|
-
if (!this.retryMethods.includes(method)) return false;
|
|
44843
|
-
if (!(err instanceof FrappeAPIError)) return false;
|
|
44844
|
-
if (err.status === 0) return true;
|
|
44845
|
-
return this.retryStatuses.includes(err.status);
|
|
45225
|
+
const badges = [];
|
|
45226
|
+
if (priority) badges.push({ label: priority, tone: priorityTone(priority) });
|
|
45227
|
+
const slaDate = row.resolution_by;
|
|
45228
|
+
if (isDateOverdue(slaDate) && columnId !== "resolved" && columnId !== "closed") {
|
|
45229
|
+
badges.push({ label: "SLA breach", tone: "error" });
|
|
44846
45230
|
}
|
|
44847
|
-
|
|
44848
|
-
|
|
44849
|
-
|
|
44850
|
-
return err.retryAfterMs;
|
|
44851
|
-
}
|
|
44852
|
-
return this.retryBackoffMs * 2 ** attempt;
|
|
45231
|
+
const metrics = [];
|
|
45232
|
+
if (typeof row.raised_by === "string" && row.raised_by.length > 0) {
|
|
45233
|
+
metrics.push({ label: "Raised By", value: row.raised_by });
|
|
44853
45234
|
}
|
|
44854
|
-
|
|
44855
|
-
|
|
44856
|
-
|
|
44857
|
-
|
|
44858
|
-
|
|
44859
|
-
|
|
44860
|
-
|
|
44861
|
-
if (attempt === this.retries || !this.isRetryable(err, method)) {
|
|
44862
|
-
throw err;
|
|
44863
|
-
}
|
|
44864
|
-
const delay = this.computeBackoff(attempt, err);
|
|
44865
|
-
if (delay > 0) {
|
|
44866
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
44867
|
-
}
|
|
44868
|
-
}
|
|
44869
|
-
}
|
|
44870
|
-
throw lastError;
|
|
45235
|
+
const slaDisplay = formatShortDate(slaDate);
|
|
45236
|
+
if (slaDisplay) metrics.push({ label: "SLA", value: slaDisplay });
|
|
45237
|
+
const openedDisplay = formatShortDate(row.opening_date);
|
|
45238
|
+
if (openedDisplay) metrics.push({ label: "Opened", value: openedDisplay });
|
|
45239
|
+
const resolvedDisplay = formatShortDate(row.resolution_date);
|
|
45240
|
+
if (resolvedDisplay) {
|
|
45241
|
+
metrics.push({ label: "Resolved", value: resolvedDisplay });
|
|
44871
45242
|
}
|
|
44872
|
-
|
|
44873
|
-
|
|
44874
|
-
|
|
44875
|
-
|
|
44876
|
-
|
|
44877
|
-
|
|
44878
|
-
|
|
44879
|
-
|
|
44880
|
-
|
|
44881
|
-
|
|
44882
|
-
|
|
44883
|
-
|
|
44884
|
-
|
|
44885
|
-
|
|
44886
|
-
|
|
44887
|
-
|
|
44888
|
-
|
|
44889
|
-
|
|
44890
|
-
|
|
44891
|
-
|
|
44892
|
-
|
|
44893
|
-
|
|
44894
|
-
|
|
44895
|
-
|
|
44896
|
-
|
|
44897
|
-
|
|
44898
|
-
|
|
44899
|
-
|
|
44900
|
-
|
|
44901
|
-
|
|
44902
|
-
|
|
44903
|
-
if (
|
|
44904
|
-
|
|
44905
|
-
responseBody = JSON.parse(rawText);
|
|
44906
|
-
} catch {
|
|
44907
|
-
responseBody = rawText;
|
|
44908
|
-
}
|
|
45243
|
+
const assignee = parseFirstAssignee(row._assign);
|
|
45244
|
+
return {
|
|
45245
|
+
id: String(row.name ?? ""),
|
|
45246
|
+
title: String(row.subject ?? row.name ?? "Untitled issue"),
|
|
45247
|
+
subtitle,
|
|
45248
|
+
columnId,
|
|
45249
|
+
accent: ISSUE_COLUMNS.find((column) => column.id === columnId)?.color,
|
|
45250
|
+
badges,
|
|
45251
|
+
metrics,
|
|
45252
|
+
dueDate: typeof slaDate === "string" ? slaDate : void 0,
|
|
45253
|
+
assignee
|
|
45254
|
+
};
|
|
45255
|
+
}
|
|
45256
|
+
var issueKanbanAdapter = {
|
|
45257
|
+
doctype: "Issue",
|
|
45258
|
+
getColumns() {
|
|
45259
|
+
return ISSUE_COLUMNS.map((column) => ({
|
|
45260
|
+
id: column.id,
|
|
45261
|
+
label: column.label,
|
|
45262
|
+
color: column.color,
|
|
45263
|
+
count: 0
|
|
45264
|
+
}));
|
|
45265
|
+
},
|
|
45266
|
+
getAllowedTransitions() {
|
|
45267
|
+
return ISSUE_ALLOWED_TRANSITIONS;
|
|
45268
|
+
},
|
|
45269
|
+
getListFields() {
|
|
45270
|
+
return ISSUE_LIST_FIELDS;
|
|
45271
|
+
},
|
|
45272
|
+
buildListFilters(input) {
|
|
45273
|
+
const filters = [];
|
|
45274
|
+
if (typeof input.status === "string" && input.status.length > 0) {
|
|
45275
|
+
filters.push(["status", "=", input.status]);
|
|
44909
45276
|
}
|
|
44910
|
-
if (
|
|
44911
|
-
|
|
44912
|
-
if (typeof responseBody === "object" && responseBody !== null) {
|
|
44913
|
-
const rb = responseBody;
|
|
44914
|
-
const excType = rb.exc_type;
|
|
44915
|
-
const baseMsg = rb.message ?? excType ?? response.statusText;
|
|
44916
|
-
const serverDetails = extractServerMessages(rb._server_messages);
|
|
44917
|
-
msg = serverDetails ? `${baseMsg}: ${serverDetails}` : baseMsg;
|
|
44918
|
-
} else if (typeof responseBody === "string" && responseBody.length > 0) {
|
|
44919
|
-
msg = responseBody.slice(0, 200);
|
|
44920
|
-
}
|
|
44921
|
-
const retryAfterMs = parseRetryAfter(
|
|
44922
|
-
response.headers.get("retry-after")
|
|
44923
|
-
);
|
|
44924
|
-
throw new FrappeAPIError(
|
|
44925
|
-
`${method} ${path} failed: ${msg}`,
|
|
44926
|
-
response.status,
|
|
44927
|
-
responseBody,
|
|
44928
|
-
retryAfterMs
|
|
44929
|
-
);
|
|
45277
|
+
if (typeof input.priority === "string" && input.priority.length > 0) {
|
|
45278
|
+
filters.push(["priority", "=", input.priority]);
|
|
44930
45279
|
}
|
|
44931
|
-
|
|
44932
|
-
|
|
44933
|
-
// ── Resource CRUD ───────────────────────────────────────────────────────────
|
|
44934
|
-
/**
|
|
44935
|
-
* List documents of a DocType.
|
|
44936
|
-
* Frappe list API: GET /api/resource/{doctype}?fields=...&filters=...
|
|
44937
|
-
*/
|
|
44938
|
-
async list(doctype, options = {}) {
|
|
44939
|
-
const params = new URLSearchParams();
|
|
44940
|
-
if (options.fields && options.fields.length > 0) {
|
|
44941
|
-
params.set("fields", JSON.stringify(options.fields));
|
|
45280
|
+
if (typeof input.customer === "string" && input.customer.length > 0) {
|
|
45281
|
+
filters.push(["customer", "=", input.customer]);
|
|
44942
45282
|
}
|
|
44943
|
-
if (
|
|
44944
|
-
|
|
45283
|
+
if (typeof input.raised_by === "string" && input.raised_by.length > 0) {
|
|
45284
|
+
filters.push(["raised_by", "=", input.raised_by]);
|
|
44945
45285
|
}
|
|
44946
|
-
|
|
44947
|
-
|
|
45286
|
+
return filters;
|
|
45287
|
+
},
|
|
45288
|
+
buildCards(rows) {
|
|
45289
|
+
return rows.map(buildIssueCard);
|
|
45290
|
+
},
|
|
45291
|
+
validateTransition(move) {
|
|
45292
|
+
const match2 = ISSUE_ALLOWED_TRANSITIONS.find(
|
|
45293
|
+
(transition) => transition.fromColumn === move.fromColumn && transition.toColumn === move.toColumn && transition.allowed
|
|
45294
|
+
);
|
|
45295
|
+
if (!match2) {
|
|
45296
|
+
return { allowed: false, reason: "Issue transition is not allowed" };
|
|
44948
45297
|
}
|
|
44949
|
-
|
|
44950
|
-
|
|
45298
|
+
return { allowed: true };
|
|
45299
|
+
},
|
|
45300
|
+
async executeMove(move, ctx) {
|
|
45301
|
+
const currentIssue = await ctx.client.get("Issue", move.cardId);
|
|
45302
|
+
const serverColumn = columnIdForIssueStatus(currentIssue.status);
|
|
45303
|
+
if (serverColumn !== move.fromColumn) {
|
|
45304
|
+
return {
|
|
45305
|
+
ok: false,
|
|
45306
|
+
cardId: move.cardId,
|
|
45307
|
+
fromColumn: serverColumn,
|
|
45308
|
+
toColumn: move.toColumn,
|
|
45309
|
+
errorMessage: `Issue moved on the server from ${move.fromColumn} to ${serverColumn}. Refresh the board and try again.`
|
|
45310
|
+
};
|
|
44951
45311
|
}
|
|
44952
|
-
|
|
44953
|
-
|
|
45312
|
+
const validation = this.validateTransition(move);
|
|
45313
|
+
if (!validation.allowed) {
|
|
45314
|
+
return {
|
|
45315
|
+
ok: false,
|
|
45316
|
+
cardId: move.cardId,
|
|
45317
|
+
fromColumn: move.fromColumn,
|
|
45318
|
+
toColumn: move.toColumn,
|
|
45319
|
+
errorMessage: validation.reason
|
|
45320
|
+
};
|
|
44954
45321
|
}
|
|
44955
|
-
|
|
44956
|
-
|
|
44957
|
-
|
|
44958
|
-
|
|
44959
|
-
|
|
44960
|
-
|
|
44961
|
-
|
|
44962
|
-
|
|
44963
|
-
|
|
44964
|
-
|
|
44965
|
-
|
|
44966
|
-
|
|
44967
|
-
|
|
44968
|
-
|
|
44969
|
-
|
|
44970
|
-
|
|
44971
|
-
|
|
44972
|
-
|
|
44973
|
-
|
|
44974
|
-
|
|
44975
|
-
* Create a new document.
|
|
44976
|
-
* POST /api/resource/{doctype}
|
|
44977
|
-
*/
|
|
44978
|
-
async create(doctype, data) {
|
|
44979
|
-
const res = await this.request(
|
|
44980
|
-
"POST",
|
|
44981
|
-
`/api/resource/${encodeURIComponent(doctype)}`,
|
|
44982
|
-
{ data: { ...data, doctype } }
|
|
44983
|
-
);
|
|
44984
|
-
return res.data;
|
|
44985
|
-
}
|
|
44986
|
-
/**
|
|
44987
|
-
* Update an existing document (partial update).
|
|
44988
|
-
* PUT /api/resource/{doctype}/{name}
|
|
44989
|
-
*/
|
|
44990
|
-
async update(doctype, name, data) {
|
|
44991
|
-
const res = await this.request(
|
|
44992
|
-
"PUT",
|
|
44993
|
-
`/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(name)}`,
|
|
44994
|
-
{ data }
|
|
44995
|
-
);
|
|
44996
|
-
return res.data;
|
|
45322
|
+
const status = STATUS_BY_COLUMN3.get(move.toColumn);
|
|
45323
|
+
if (!status) {
|
|
45324
|
+
return {
|
|
45325
|
+
ok: false,
|
|
45326
|
+
cardId: move.cardId,
|
|
45327
|
+
fromColumn: move.fromColumn,
|
|
45328
|
+
toColumn: move.toColumn,
|
|
45329
|
+
errorMessage: "Unknown Issue column"
|
|
45330
|
+
};
|
|
45331
|
+
}
|
|
45332
|
+
const serverIssue = await ctx.client.update("Issue", move.cardId, {
|
|
45333
|
+
status
|
|
45334
|
+
});
|
|
45335
|
+
return {
|
|
45336
|
+
ok: true,
|
|
45337
|
+
cardId: move.cardId,
|
|
45338
|
+
fromColumn: serverColumn,
|
|
45339
|
+
toColumn: move.toColumn,
|
|
45340
|
+
serverCard: buildIssueCard(serverIssue)
|
|
45341
|
+
};
|
|
44997
45342
|
}
|
|
44998
|
-
|
|
44999
|
-
|
|
45000
|
-
|
|
45001
|
-
|
|
45002
|
-
|
|
45003
|
-
|
|
45004
|
-
|
|
45005
|
-
|
|
45006
|
-
|
|
45343
|
+
};
|
|
45344
|
+
|
|
45345
|
+
// src/tools/kanban.ts
|
|
45346
|
+
var ADAPTERS = {
|
|
45347
|
+
task: taskKanbanAdapter,
|
|
45348
|
+
opportunity: opportunityKanbanAdapter,
|
|
45349
|
+
issue: issueKanbanAdapter
|
|
45350
|
+
};
|
|
45351
|
+
function getAdapter(doctype) {
|
|
45352
|
+
const definition = getKanbanBoardDefinition(doctype);
|
|
45353
|
+
if (!definition) {
|
|
45354
|
+
throw new Error(`[erpnext_kanban] Unsupported kanban doctype: ${doctype}`);
|
|
45007
45355
|
}
|
|
45008
|
-
|
|
45009
|
-
|
|
45010
|
-
|
|
45011
|
-
*/
|
|
45012
|
-
async callMethod(method, args = {}) {
|
|
45013
|
-
const res = await this.request(
|
|
45014
|
-
"POST",
|
|
45015
|
-
`/api/method/${method}`,
|
|
45016
|
-
args
|
|
45017
|
-
);
|
|
45018
|
-
return res.message;
|
|
45356
|
+
const adapter = ADAPTERS[definition.adapterKey];
|
|
45357
|
+
if (!adapter) {
|
|
45358
|
+
throw new Error(`[erpnext_kanban] No adapter registered for ${doctype}`);
|
|
45019
45359
|
}
|
|
45020
|
-
};
|
|
45021
|
-
|
|
45022
|
-
function
|
|
45023
|
-
|
|
45024
|
-
const
|
|
45025
|
-
|
|
45026
|
-
const apiSecret = env6("ERPNEXT_API_SECRET");
|
|
45027
|
-
if (!url) {
|
|
45028
|
-
throw new Error(
|
|
45029
|
-
"[lib/erpnext] ERPNEXT_URL is required. Set it to your ERPNext instance URL, e.g. http://localhost:8000"
|
|
45030
|
-
);
|
|
45360
|
+
return { definition, adapter };
|
|
45361
|
+
}
|
|
45362
|
+
function withColumnCounts(columns, cards) {
|
|
45363
|
+
const counts = /* @__PURE__ */ new Map();
|
|
45364
|
+
for (const card of cards) {
|
|
45365
|
+
counts.set(card.columnId, (counts.get(card.columnId) ?? 0) + 1);
|
|
45031
45366
|
}
|
|
45032
|
-
|
|
45033
|
-
|
|
45034
|
-
|
|
45035
|
-
|
|
45367
|
+
return columns.map((column) => ({
|
|
45368
|
+
...column,
|
|
45369
|
+
count: counts.get(column.id) ?? 0
|
|
45370
|
+
}));
|
|
45371
|
+
}
|
|
45372
|
+
var kanbanTools = [
|
|
45373
|
+
{
|
|
45374
|
+
name: "erpnext_kanban_get_board",
|
|
45375
|
+
annotations: { readOnlyHint: true },
|
|
45376
|
+
category: "kanban",
|
|
45377
|
+
_meta: KANBAN_META,
|
|
45378
|
+
description: "Get a normalized kanban board for a supported ERPNext DocType. Supports Task, Opportunity, and Issue, with pagination and MCP App metadata.",
|
|
45379
|
+
inputSchema: {
|
|
45380
|
+
type: "object",
|
|
45381
|
+
properties: {
|
|
45382
|
+
doctype: {
|
|
45383
|
+
type: "string",
|
|
45384
|
+
description: "Kanban-enabled ERPNext DocType",
|
|
45385
|
+
enum: ["Task", "Opportunity", "Issue"]
|
|
45386
|
+
},
|
|
45387
|
+
limit: { type: "number", description: "Page size (default 50)" },
|
|
45388
|
+
offset: {
|
|
45389
|
+
type: "number",
|
|
45390
|
+
description: "Pagination offset (default 0)"
|
|
45391
|
+
},
|
|
45392
|
+
project: {
|
|
45393
|
+
type: "string",
|
|
45394
|
+
description: "Optional Task project filter"
|
|
45395
|
+
},
|
|
45396
|
+
priority: {
|
|
45397
|
+
type: "string",
|
|
45398
|
+
description: "Optional Task priority filter",
|
|
45399
|
+
enum: ["Low", "Medium", "High", "Urgent"]
|
|
45400
|
+
},
|
|
45401
|
+
status: {
|
|
45402
|
+
type: "string",
|
|
45403
|
+
description: "Optional Opportunity status filter",
|
|
45404
|
+
enum: ["Open", "Replied", "Quotation", "Converted", "Closed", "Lost"]
|
|
45405
|
+
},
|
|
45406
|
+
opportunity_owner: {
|
|
45407
|
+
type: "string",
|
|
45408
|
+
description: "Optional Opportunity owner filter"
|
|
45409
|
+
},
|
|
45410
|
+
party_name: {
|
|
45411
|
+
type: "string",
|
|
45412
|
+
description: "Optional Opportunity party filter"
|
|
45413
|
+
},
|
|
45414
|
+
customer: {
|
|
45415
|
+
type: "string",
|
|
45416
|
+
description: "Optional Issue customer filter"
|
|
45417
|
+
},
|
|
45418
|
+
raised_by: {
|
|
45419
|
+
type: "string",
|
|
45420
|
+
description: "Optional Issue reporter email filter"
|
|
45421
|
+
}
|
|
45422
|
+
},
|
|
45423
|
+
required: ["doctype"]
|
|
45424
|
+
},
|
|
45425
|
+
handler: async (input, ctx) => {
|
|
45426
|
+
const doctype = String(input.doctype ?? "");
|
|
45427
|
+
const { definition, adapter } = getAdapter(doctype);
|
|
45428
|
+
const limit = Math.max(1, Number(input.limit ?? 50));
|
|
45429
|
+
const offset = Math.max(0, Number(input.offset ?? 0));
|
|
45430
|
+
const rows = await ctx.client.list(doctype, {
|
|
45431
|
+
fields: adapter.getListFields(),
|
|
45432
|
+
filters: adapter.buildListFilters(input),
|
|
45433
|
+
limit: limit + 1,
|
|
45434
|
+
limit_start: offset,
|
|
45435
|
+
order_by: "modified desc"
|
|
45436
|
+
});
|
|
45437
|
+
const hasMore = rows.length > limit;
|
|
45438
|
+
const visibleRows = rows.slice(0, limit);
|
|
45439
|
+
const cards = adapter.buildCards(visibleRows);
|
|
45440
|
+
const board = {
|
|
45441
|
+
boardId: `${doctype.toLowerCase()}-board`,
|
|
45442
|
+
title: definition.title,
|
|
45443
|
+
doctype,
|
|
45444
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45445
|
+
moveToolName: definition.moveToolName,
|
|
45446
|
+
refreshArguments: { ...input, doctype, limit, offset },
|
|
45447
|
+
columns: withColumnCounts(adapter.getColumns(), cards),
|
|
45448
|
+
cards,
|
|
45449
|
+
allowedTransitions: adapter.getAllowedTransitions(),
|
|
45450
|
+
capabilities: { canMoveCards: true },
|
|
45451
|
+
pagination: {
|
|
45452
|
+
limit,
|
|
45453
|
+
offset,
|
|
45454
|
+
loadedCount: offset + visibleRows.length,
|
|
45455
|
+
hasMore
|
|
45456
|
+
}
|
|
45457
|
+
};
|
|
45458
|
+
return {
|
|
45459
|
+
...board,
|
|
45460
|
+
_meta: KANBAN_META
|
|
45461
|
+
};
|
|
45462
|
+
}
|
|
45463
|
+
},
|
|
45464
|
+
{
|
|
45465
|
+
name: "erpnext_kanban_move_card",
|
|
45466
|
+
category: "kanban",
|
|
45467
|
+
description: "Move a kanban card for a supported ERPNext DocType. Returns structured success or business error details for MCP App reconciliation.",
|
|
45468
|
+
inputSchema: {
|
|
45469
|
+
type: "object",
|
|
45470
|
+
properties: {
|
|
45471
|
+
doctype: {
|
|
45472
|
+
type: "string",
|
|
45473
|
+
description: "Kanban-enabled ERPNext DocType",
|
|
45474
|
+
enum: ["Task", "Opportunity", "Issue"]
|
|
45475
|
+
},
|
|
45476
|
+
card_id: { type: "string", description: "Card/document identifier" },
|
|
45477
|
+
from_column: {
|
|
45478
|
+
type: "string",
|
|
45479
|
+
description: "Source column identifier"
|
|
45480
|
+
},
|
|
45481
|
+
to_column: {
|
|
45482
|
+
type: "string",
|
|
45483
|
+
description: "Destination column identifier"
|
|
45484
|
+
}
|
|
45485
|
+
},
|
|
45486
|
+
required: ["doctype", "card_id", "from_column", "to_column"]
|
|
45487
|
+
},
|
|
45488
|
+
handler: async (input, ctx) => {
|
|
45489
|
+
const doctype = String(input.doctype ?? "");
|
|
45490
|
+
const { adapter } = getAdapter(doctype);
|
|
45491
|
+
return await adapter.executeMove({
|
|
45492
|
+
doctype,
|
|
45493
|
+
cardId: String(input.card_id ?? ""),
|
|
45494
|
+
fromColumn: String(input.from_column ?? ""),
|
|
45495
|
+
toColumn: String(input.to_column ?? "")
|
|
45496
|
+
}, ctx);
|
|
45497
|
+
}
|
|
45036
45498
|
}
|
|
45037
|
-
|
|
45038
|
-
|
|
45499
|
+
];
|
|
45500
|
+
|
|
45501
|
+
// src/tools/mod.ts
|
|
45502
|
+
var toolsByCategory = {
|
|
45503
|
+
sales: salesTools,
|
|
45504
|
+
inventory: inventoryTools,
|
|
45505
|
+
accounting: accountingTools,
|
|
45506
|
+
hr: hrTools,
|
|
45507
|
+
project: projectTools,
|
|
45508
|
+
purchasing: purchasingTools,
|
|
45509
|
+
delivery: deliveryTools,
|
|
45510
|
+
manufacturing: manufacturingTools,
|
|
45511
|
+
crm: crmTools,
|
|
45512
|
+
assets: assetsTools,
|
|
45513
|
+
operations: operationsTools,
|
|
45514
|
+
setup: setupTools,
|
|
45515
|
+
analytics: analyticsTools,
|
|
45516
|
+
kanban: kanbanTools
|
|
45517
|
+
};
|
|
45518
|
+
var allTools = [
|
|
45519
|
+
...salesTools,
|
|
45520
|
+
...inventoryTools,
|
|
45521
|
+
...accountingTools,
|
|
45522
|
+
...hrTools,
|
|
45523
|
+
...projectTools,
|
|
45524
|
+
...purchasingTools,
|
|
45525
|
+
...deliveryTools,
|
|
45526
|
+
...manufacturingTools,
|
|
45527
|
+
...crmTools,
|
|
45528
|
+
...assetsTools,
|
|
45529
|
+
...operationsTools,
|
|
45530
|
+
...setupTools,
|
|
45531
|
+
...analyticsTools,
|
|
45532
|
+
...kanbanTools
|
|
45533
|
+
];
|
|
45534
|
+
function getToolsByCategory(category) {
|
|
45535
|
+
return toolsByCategory[category] ?? [];
|
|
45536
|
+
}
|
|
45537
|
+
function getToolByName(name) {
|
|
45538
|
+
return allTools.find((t) => t.name === name);
|
|
45039
45539
|
}
|
|
45040
45540
|
|
|
45041
45541
|
// src/tools/ui-refresh.ts
|
|
@@ -45332,6 +45832,37 @@ function resolveViewerDistPath2(moduleUrl, viewerName, exists) {
|
|
|
45332
45832
|
return null;
|
|
45333
45833
|
}
|
|
45334
45834
|
|
|
45835
|
+
// src/cache/warm.ts
|
|
45836
|
+
async function warmCache() {
|
|
45837
|
+
const configured = env6("MCP_CACHE_WARM_TOOLS");
|
|
45838
|
+
if (!configured?.trim()) return;
|
|
45839
|
+
const client = getFrappeClient();
|
|
45840
|
+
const names = configured.split(",").map((s) => s.trim()).filter(Boolean);
|
|
45841
|
+
for (const name of names) {
|
|
45842
|
+
const tool = getToolByName(name);
|
|
45843
|
+
if (!tool) {
|
|
45844
|
+
console.error(
|
|
45845
|
+
`[mcp-erpnext] Cache warm: unknown tool "${name}", skipping`
|
|
45846
|
+
);
|
|
45847
|
+
continue;
|
|
45848
|
+
}
|
|
45849
|
+
if (!tool.annotations?.readOnlyHint) {
|
|
45850
|
+
console.error(
|
|
45851
|
+
`[mcp-erpnext] Cache warm: "${name}" is not read-only, refusing to call it, skipping`
|
|
45852
|
+
);
|
|
45853
|
+
continue;
|
|
45854
|
+
}
|
|
45855
|
+
try {
|
|
45856
|
+
await tool.handler({}, { client });
|
|
45857
|
+
} catch (err) {
|
|
45858
|
+
console.error(
|
|
45859
|
+
`[mcp-erpnext] Cache warm: "${name}" failed (non-fatal):`,
|
|
45860
|
+
err
|
|
45861
|
+
);
|
|
45862
|
+
}
|
|
45863
|
+
}
|
|
45864
|
+
}
|
|
45865
|
+
|
|
45335
45866
|
// server.ts
|
|
45336
45867
|
var DEFAULT_HTTP_PORT = 3012;
|
|
45337
45868
|
async function main() {
|
|
@@ -45356,7 +45887,7 @@ async function main() {
|
|
|
45356
45887
|
);
|
|
45357
45888
|
const server = new McpApp({
|
|
45358
45889
|
name: "mcp-erpnext",
|
|
45359
|
-
version: "2.
|
|
45890
|
+
version: "2.5.0",
|
|
45360
45891
|
maxConcurrent: 10,
|
|
45361
45892
|
backpressureStrategy: "queue",
|
|
45362
45893
|
validateSchema: true,
|
|
@@ -45401,6 +45932,9 @@ async function main() {
|
|
|
45401
45932
|
console.error(
|
|
45402
45933
|
`[mcp-erpnext] Initialized \u2014 ${toolsClient.count} tools${categories ? ` (categories: ${categories.join(", ")})` : ""}`
|
|
45403
45934
|
);
|
|
45935
|
+
warmCache().catch((err) => {
|
|
45936
|
+
console.error("[mcp-erpnext] Cache warm failed (non-fatal):", err);
|
|
45937
|
+
});
|
|
45404
45938
|
if (httpFlag) {
|
|
45405
45939
|
const isLoopback = hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost";
|
|
45406
45940
|
if (!isLoopback) {
|