""" Level 8 — Business action tools (ERPNext shortcuts). High-value operations: convert documents, approve, transfer stock, etc. These call ERPNext's built-in mapper/make functions. """ import json from mcp.types import Tool from frappe_mcp.client.frappe_api import FrappeClient def tools() -> list[Tool]: return [ # ── Sales / CRM ────────────────────────────────────────────────────── Tool( name="erpnext_create_quotation_from_opportunity", description="Create a Quotation from an Opportunity.", inputSchema={ "type": "object", "required": ["opportunity_name"], "properties": {"opportunity_name": {"type": "string"}}, }, ), Tool( name="erpnext_create_sales_order_from_quotation", description="Create a Sales Order from a Quotation.", inputSchema={ "type": "object", "required": ["quotation_name"], "properties": {"quotation_name": {"type": "string"}}, }, ), Tool( name="erpnext_create_sales_invoice_from_sales_order", description="Create a Sales Invoice from a Sales Order.", inputSchema={ "type": "object", "required": ["sales_order_name"], "properties": {"sales_order_name": {"type": "string"}}, }, ), Tool( name="erpnext_create_delivery_note_from_sales_order", description="Create a Delivery Note from a Sales Order.", inputSchema={ "type": "object", "required": ["sales_order_name"], "properties": {"sales_order_name": {"type": "string"}}, }, ), Tool( name="erpnext_create_payment_entry_for_invoice", description="Create a Payment Entry for a Sales Invoice or Purchase Invoice.", inputSchema={ "type": "object", "required": ["invoice_doctype", "invoice_name"], "properties": { "invoice_doctype": {"type": "string", "enum": ["Sales Invoice", "Purchase Invoice"]}, "invoice_name": {"type": "string"}, }, }, ), # ── Buying ─────────────────────────────────────────────────────────── Tool( name="erpnext_create_purchase_order_from_supplier_quotation", description="Create a Purchase Order from a Supplier Quotation.", inputSchema={ "type": "object", "required": ["supplier_quotation_name"], "properties": {"supplier_quotation_name": {"type": "string"}}, }, ), Tool( name="erpnext_create_purchase_receipt_from_purchase_order", description="Create a Purchase Receipt from a Purchase Order.", inputSchema={ "type": "object", "required": ["purchase_order_name"], "properties": {"purchase_order_name": {"type": "string"}}, }, ), Tool( name="erpnext_create_purchase_invoice_from_purchase_receipt", description="Create a Purchase Invoice from a Purchase Receipt.", inputSchema={ "type": "object", "required": ["purchase_receipt_name"], "properties": {"purchase_receipt_name": {"type": "string"}}, }, ), # ── Stock ──────────────────────────────────────────────────────────── Tool( name="erpnext_get_item_stock_balance", description="Get current stock balance for an item, optionally at a specific warehouse.", inputSchema={ "type": "object", "required": ["item_code"], "properties": { "item_code": {"type": "string"}, "warehouse": {"type": "string"}, "posting_date": {"type": "string", "description": "YYYY-MM-DD (defaults to today)"}, }, }, ), Tool( name="erpnext_transfer_stock", description="Transfer stock between warehouses via a Material Transfer Stock Entry.", inputSchema={ "type": "object", "required": ["item_code", "qty", "from_warehouse", "to_warehouse"], "properties": { "item_code": {"type": "string"}, "qty": {"type": "number"}, "from_warehouse": {"type": "string"}, "to_warehouse": {"type": "string"}, "posting_date": {"type": "string"}, "remarks": {"type": "string"}, }, }, ), Tool( name="erpnext_create_stock_entry", description=( "Create a Stock Entry of any type: " "Material Issue, Material Receipt, Material Transfer, Manufacture, Repack." ), inputSchema={ "type": "object", "required": ["stock_entry_type", "items"], "properties": { "stock_entry_type": { "type": "string", "enum": ["Material Issue", "Material Receipt", "Material Transfer", "Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"], }, "items": { "type": "array", "description": "List of {item_code, qty, s_warehouse, t_warehouse} objects", "items": {"type": "object"}, }, "posting_date": {"type": "string"}, "remarks": {"type": "string"}, }, }, ), # ── HR ─────────────────────────────────────────────────────────────── Tool( name="erpnext_approve_leave_application", description="Approve a Leave Application.", inputSchema={ "type": "object", "required": ["leave_application_name"], "properties": {"leave_application_name": {"type": "string"}}, }, ), Tool( name="erpnext_reject_leave_application", description="Reject a Leave Application.", inputSchema={ "type": "object", "required": ["leave_application_name"], "properties": { "leave_application_name": {"type": "string"}, "reason": {"type": "string"}, }, }, ), Tool( name="erpnext_mark_attendance", description="Mark attendance for an employee on a specific date.", inputSchema={ "type": "object", "required": ["employee", "attendance_date", "status"], "properties": { "employee": {"type": "string"}, "attendance_date": {"type": "string", "description": "YYYY-MM-DD"}, "status": {"type": "string", "enum": ["Present", "Absent", "Half Day", "Work From Home", "On Leave"]}, "shift": {"type": "string"}, }, }, ), Tool( name="erpnext_approve_expense_claim", description="Approve an Expense Claim.", inputSchema={ "type": "object", "required": ["expense_claim_name"], "properties": {"expense_claim_name": {"type": "string"}}, }, ), # ── Support ────────────────────────────────────────────────────────── Tool( name="erpnext_create_issue", description="Create a new Support Issue.", inputSchema={ "type": "object", "required": ["subject"], "properties": { "subject": {"type": "string"}, "customer": {"type": "string"}, "description": {"type": "string"}, "priority": {"type": "string", "enum": ["Low", "Medium", "High", "Urgent"], "default": "Medium"}, "issue_type": {"type": "string"}, "raised_by": {"type": "string"}, }, }, ), Tool( name="erpnext_close_issue", description="Close a Support Issue.", inputSchema={ "type": "object", "required": ["issue_name"], "properties": { "issue_name": {"type": "string"}, "resolution_details": {"type": "string"}, }, }, ), # ── Projects ───────────────────────────────────────────────────────── Tool( name="erpnext_create_task", description="Create a Task in a Project.", inputSchema={ "type": "object", "required": ["subject"], "properties": { "subject": {"type": "string"}, "project": {"type": "string"}, "assigned_to": {"type": "string"}, "exp_start_date": {"type": "string"}, "exp_end_date": {"type": "string"}, "priority": {"type": "string", "enum": ["Low", "Medium", "High", "Urgent"]}, "description": {"type": "string"}, }, }, ), Tool( name="erpnext_update_task_status", description="Update the status of a Task.", inputSchema={ "type": "object", "required": ["task_name", "status"], "properties": { "task_name": {"type": "string"}, "status": {"type": "string", "enum": ["Open", "Working", "Pending Review", "Overdue", "Template", "Cancelled", "Completed"]}, }, }, ), Tool( name="erpnext_log_timesheet_entry", description="Log a timesheet entry for an employee on a task or project.", inputSchema={ "type": "object", "required": ["employee", "from_time", "to_time"], "properties": { "employee": {"type": "string"}, "from_time": {"type": "string", "description": "ISO datetime"}, "to_time": {"type": "string", "description": "ISO datetime"}, "project": {"type": "string"}, "task": {"type": "string"}, "activity_type": {"type": "string"}, "description": {"type": "string"}, }, }, ), ] def handlers() -> dict: return { "erpnext_create_quotation_from_opportunity": _make_quotation_from_opportunity, "erpnext_create_sales_order_from_quotation": _make_so_from_quotation, "erpnext_create_sales_invoice_from_sales_order": _make_si_from_so, "erpnext_create_delivery_note_from_sales_order": _make_dn_from_so, "erpnext_create_payment_entry_for_invoice": _make_payment_entry, "erpnext_create_purchase_order_from_supplier_quotation": _make_po_from_sq, "erpnext_create_purchase_receipt_from_purchase_order": _make_pr_from_po, "erpnext_create_purchase_invoice_from_purchase_receipt": _make_pi_from_pr, "erpnext_get_item_stock_balance": _get_item_stock_balance, "erpnext_transfer_stock": _transfer_stock, "erpnext_create_stock_entry": _create_stock_entry, "erpnext_approve_leave_application": _approve_leave, "erpnext_reject_leave_application": _reject_leave, "erpnext_mark_attendance": _mark_attendance, "erpnext_approve_expense_claim": _approve_expense_claim, "erpnext_create_issue": _create_issue, "erpnext_close_issue": _close_issue, "erpnext_create_task": _create_task, "erpnext_update_task_status": _update_task_status, "erpnext_log_timesheet_entry": _log_timesheet, } async def _make_quotation_from_opportunity(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.crm.doctype.opportunity.opportunity.make_quotation", source_name=args["opportunity_name"], ) return json.dumps(result, indent=2) async def _make_so_from_quotation(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.selling.doctype.quotation.quotation.make_sales_order", source_name=args["quotation_name"], ) return json.dumps(result, indent=2) async def _make_si_from_so(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice", source_name=args["sales_order_name"], ) return json.dumps(result, indent=2) async def _make_dn_from_so(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", source_name=args["sales_order_name"], ) return json.dumps(result, indent=2) async def _make_payment_entry(args: dict) -> str: client = FrappeClient() method = ( "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account" if args["invoice_doctype"] == "Sales Invoice" else "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_payment_entry" ) result = await client.call_method( "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry", dt=args["invoice_doctype"], dn=args["invoice_name"], ) return json.dumps(result, indent=2) async def _make_po_from_sq(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_purchase_order", source_name=args["supplier_quotation_name"], ) return json.dumps(result, indent=2) async def _make_pr_from_po(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt", source_name=args["purchase_order_name"], ) return json.dumps(result, indent=2) async def _make_pi_from_pr(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.buying.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice", source_name=args["purchase_receipt_name"], ) return json.dumps(result, indent=2) async def _get_item_stock_balance(args: dict) -> str: client = FrappeClient() result = await client.call_method( "erpnext.stock.utils.get_stock_balance", item_code=args["item_code"], warehouse=args.get("warehouse", ""), posting_date=args.get("posting_date", ""), ) return json.dumps({"item_code": args["item_code"], "warehouse": args.get("warehouse"), "balance": result}, indent=2) async def _transfer_stock(args: dict) -> str: client = FrappeClient() payload = { "stock_entry_type": "Material Transfer", "posting_date": args.get("posting_date", ""), "remarks": args.get("remarks", ""), "items": [{ "item_code": args["item_code"], "qty": args["qty"], "s_warehouse": args["from_warehouse"], "t_warehouse": args["to_warehouse"], }], } result = await client.create_doc("Stock Entry", payload) return json.dumps(result, indent=2) async def _create_stock_entry(args: dict) -> str: client = FrappeClient() payload = { "stock_entry_type": args["stock_entry_type"], "posting_date": args.get("posting_date", ""), "remarks": args.get("remarks", ""), "items": args["items"], } result = await client.create_doc("Stock Entry", payload) return json.dumps(result, indent=2) async def _approve_leave(args: dict) -> str: client = FrappeClient() result = await client.update_doc("Leave Application", args["leave_application_name"], {"status": "Approved"}) return json.dumps(result, indent=2) async def _reject_leave(args: dict) -> str: client = FrappeClient() updates = {"status": "Rejected"} if reason := args.get("reason"): updates["reason_for_rejection"] = reason result = await client.update_doc("Leave Application", args["leave_application_name"], updates) return json.dumps(result, indent=2) async def _mark_attendance(args: dict) -> str: client = FrappeClient() payload = { "employee": args["employee"], "attendance_date": args["attendance_date"], "status": args["status"], "shift": args.get("shift", ""), "docstatus": 1, } result = await client.create_doc("Attendance", payload) return json.dumps(result, indent=2) async def _approve_expense_claim(args: dict) -> str: client = FrappeClient() result = await client.update_doc("Expense Claim", args["expense_claim_name"], {"approval_status": "Approved"}) return json.dumps(result, indent=2) async def _create_issue(args: dict) -> str: client = FrappeClient() payload = { "subject": args["subject"], "customer": args.get("customer", ""), "description": args.get("description", ""), "priority": args.get("priority", "Medium"), "issue_type": args.get("issue_type", ""), "raised_by": args.get("raised_by", ""), } result = await client.create_doc("Issue", payload) return json.dumps(result, indent=2) async def _close_issue(args: dict) -> str: client = FrappeClient() updates = {"status": "Closed"} if res := args.get("resolution_details"): updates["resolution_details"] = res result = await client.update_doc("Issue", args["issue_name"], updates) return json.dumps(result, indent=2) async def _create_task(args: dict) -> str: client = FrappeClient() result = await client.create_doc("Task", args) return json.dumps(result, indent=2) async def _update_task_status(args: dict) -> str: client = FrappeClient() result = await client.update_doc("Task", args["task_name"], {"status": args["status"]}) return json.dumps(result, indent=2) async def _log_timesheet(args: dict) -> str: client = FrappeClient() payload = { "employee": args["employee"], "time_logs": [{ "from_time": args["from_time"], "to_time": args["to_time"], "project": args.get("project", ""), "task": args.get("task", ""), "activity_type": args.get("activity_type", ""), "description": args.get("description", ""), }], } result = await client.create_doc("Timesheet", payload) return json.dumps(result, indent=2)