239 lines
9.6 KiB
TypeScript
239 lines
9.6 KiB
TypeScript
|
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||
|
|
import { ERPNextClient, truncateResponse } from "../services/erpnext-client.js";
|
||
|
|
import { z } from "zod";
|
||
|
|
|
||
|
|
export function registerSellingTools(server: McpServer, client: ERPNextClient): void {
|
||
|
|
|
||
|
|
// ─── 23. Make Sales Invoice from Sales Order ───────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_make_sales_invoice_from_so",
|
||
|
|
{
|
||
|
|
title: "Make Sales Invoice from Sales Order",
|
||
|
|
description: `Create a Sales Invoice from a submitted Sales Order.`,
|
||
|
|
inputSchema: z.object({ source_name: z.string().describe("Sales Order name") }).strict(),
|
||
|
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const result = await client.callMethod(
|
||
|
|
"erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice",
|
||
|
|
{ source_name: params.source_name }
|
||
|
|
);
|
||
|
|
return { content: [{ type: "text", text: `Sales Invoice draft created from ${params.source_name}:\n${JSON.stringify(result, null, 2).slice(0, 3000)}` }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 24. Make Delivery Note from Sales Order ───────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_make_delivery_note_from_so",
|
||
|
|
{
|
||
|
|
title: "Make Delivery Note from Sales Order",
|
||
|
|
description: `Create a Delivery Note from a submitted Sales Order.`,
|
||
|
|
inputSchema: z.object({ source_name: z.string().describe("Sales Order name") }).strict(),
|
||
|
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const result = await client.callMethod(
|
||
|
|
"erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
||
|
|
{ source_name: params.source_name }
|
||
|
|
);
|
||
|
|
return { content: [{ type: "text", text: `Delivery Note draft created from ${params.source_name}:\n${JSON.stringify(result, null, 2).slice(0, 3000)}` }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 25. Make Material Request from Sales Order ────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_make_material_request_from_so",
|
||
|
|
{
|
||
|
|
title: "Make Material Request from Sales Order",
|
||
|
|
description: `Create a Material Request from a Sales Order.`,
|
||
|
|
inputSchema: z.object({ source_name: z.string() }).strict(),
|
||
|
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const result = await client.callMethod(
|
||
|
|
"erpnext.selling.doctype.sales_order.sales_order.make_material_request",
|
||
|
|
{ source_name: params.source_name }
|
||
|
|
);
|
||
|
|
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 26. Close/Reopen Sales Order ──────────────────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_update_sales_order_status",
|
||
|
|
{
|
||
|
|
title: "Close/Reopen Sales Order",
|
||
|
|
description: `Close or reopen a Sales Order.`,
|
||
|
|
inputSchema: z.object({
|
||
|
|
name: z.string(),
|
||
|
|
status: z.enum(["Closed", "Re-open"]),
|
||
|
|
}).strict(),
|
||
|
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
await client.callMethod("erpnext.selling.doctype.sales_order.sales_order.update_status", {
|
||
|
|
status: params.status === "Re-open" ? "Draft" : params.status,
|
||
|
|
name: params.name,
|
||
|
|
});
|
||
|
|
return { content: [{ type: "text", text: `Sales Order ${params.name} ${params.status === "Closed" ? "closed" : "reopened"}.` }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 27. Make Quotation ────────────────────────────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_make_quotation",
|
||
|
|
{
|
||
|
|
title: "Create Quotation",
|
||
|
|
description: `Create a Quotation for a customer.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
- party_name: Customer or Lead name
|
||
|
|
- company: Company name
|
||
|
|
- items: Array of {item_code, qty, rate?, warehouse?}
|
||
|
|
- quotation_to: "Customer" or "Lead" (default "Customer")`,
|
||
|
|
inputSchema: z.object({
|
||
|
|
party_name: z.string(),
|
||
|
|
company: z.string(),
|
||
|
|
items: z.array(z.object({
|
||
|
|
item_code: z.string(),
|
||
|
|
qty: z.number(),
|
||
|
|
rate: z.number().optional(),
|
||
|
|
warehouse: z.string().optional(),
|
||
|
|
})),
|
||
|
|
quotation_to: z.enum(["Customer", "Lead"]).default("Customer"),
|
||
|
|
transaction_date: z.string().optional(),
|
||
|
|
}).strict(),
|
||
|
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const doc = await client.createDocument("Quotation", {
|
||
|
|
quotation_to: params.quotation_to,
|
||
|
|
party_name: params.party_name,
|
||
|
|
company: params.company,
|
||
|
|
transaction_date: params.transaction_date,
|
||
|
|
items: params.items,
|
||
|
|
});
|
||
|
|
return { content: [{ type: "text", text: `Created Quotation: ${doc.name}` }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 28. Make Sales Order from Quotation ───────────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_make_sales_order_from_quotation",
|
||
|
|
{
|
||
|
|
title: "Make Sales Order from Quotation",
|
||
|
|
description: `Create a Sales Order from a submitted Quotation.`,
|
||
|
|
inputSchema: z.object({ source_name: z.string().describe("Quotation name") }).strict(),
|
||
|
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const result = await client.callMethod(
|
||
|
|
"erpnext.selling.doctype.quotation.quotation.make_sales_order",
|
||
|
|
{ source_name: params.source_name }
|
||
|
|
);
|
||
|
|
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 29. Sales Funnel Data ─────────────────────────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_get_sales_funnel",
|
||
|
|
{
|
||
|
|
title: "Get Sales Funnel Data",
|
||
|
|
description: `Get sales funnel data showing opportunity pipeline.`,
|
||
|
|
inputSchema: z.object({
|
||
|
|
from_date: z.string(),
|
||
|
|
to_date: z.string(),
|
||
|
|
company: z.string(),
|
||
|
|
}).strict(),
|
||
|
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const result = await client.callMethod(
|
||
|
|
"erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
|
||
|
|
params
|
||
|
|
);
|
||
|
|
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// ─── 30. Get Item Details for Transaction ──────────────
|
||
|
|
server.registerTool(
|
||
|
|
"erpnext_get_item_details",
|
||
|
|
{
|
||
|
|
title: "Get Item Details for Transaction",
|
||
|
|
description: `Get item details including pricing, tax, and availability for a transaction context.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
- item_code: Item code
|
||
|
|
- company: Company name
|
||
|
|
- doctype: Parent doctype (e.g. "Sales Order", "Purchase Order")
|
||
|
|
- customer: Customer name (for selling)
|
||
|
|
- supplier: Supplier name (for buying)
|
||
|
|
- price_list: Price list name
|
||
|
|
- warehouse: Warehouse name`,
|
||
|
|
inputSchema: z.object({
|
||
|
|
item_code: z.string(),
|
||
|
|
company: z.string(),
|
||
|
|
doctype: z.string().default("Sales Order"),
|
||
|
|
customer: z.string().optional(),
|
||
|
|
supplier: z.string().optional(),
|
||
|
|
price_list: z.string().optional(),
|
||
|
|
warehouse: z.string().optional(),
|
||
|
|
currency: z.string().optional(),
|
||
|
|
conversion_rate: z.number().optional(),
|
||
|
|
}).strict(),
|
||
|
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
||
|
|
},
|
||
|
|
async (params) => {
|
||
|
|
try {
|
||
|
|
const result = await client.callMethod("erpnext.stock.get_item_details.get_item_details", {
|
||
|
|
args: {
|
||
|
|
item_code: params.item_code,
|
||
|
|
company: params.company,
|
||
|
|
doctype: params.doctype,
|
||
|
|
customer: params.customer,
|
||
|
|
supplier: params.supplier,
|
||
|
|
selling_price_list: params.price_list,
|
||
|
|
buying_price_list: params.price_list,
|
||
|
|
warehouse: params.warehouse,
|
||
|
|
currency: params.currency,
|
||
|
|
conversion_rate: params.conversion_rate,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
|
||
|
|
} catch (error) {
|
||
|
|
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|