/** * 星环OPC中心 — 智能记账工具 * 支持自然语言记账和智能分类 */ import { Type, type Static } from "@sinclair/typebox"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import type { OpcDatabase } from "../db/index.js"; import { json, toolError } from "../utils/tool-helper.js"; import { parseAccountingIntent, suggestCategory } from "../opc/accounting-parser.js"; const SmartAccountingSchema = Type.Union([ Type.Object({ action: Type.Literal("natural_language_record"), company_id: Type.String({ description: "公司 ID" }), input: Type.String({ description: "自然语言记账输入,如:今天请客户吃饭花了 500 块" }), confirm: Type.Optional(Type.Boolean({ description: "是否确认记账(默认 false,先预览)" })), }), Type.Object({ action: Type.Literal("suggest_category"), description: Type.String({ description: "交易描述" }), }), ]); type SmartAccountingParams = Static; export function registerSmartAccountingTool(api: OpenClawPluginApi, db: OpcDatabase): void { api.registerTool( { name: "opc_smart_accounting", label: "OPC 智能记账", description: "智能记账工具,支持自然语言记账和智能分类。操作: " + "natural_language_record(自然语言记账), suggest_category(建议分类)", parameters: SmartAccountingSchema, async execute(_toolCallId, params) { const p = params as SmartAccountingParams; try { switch (p.action) { case "natural_language_record": { // 使用 LLM 解析自然语言输入 const intent = await parseAccountingIntent(p.input, api); if (intent.confidence < 50) { return toolError( `解析置信度过低(${intent.confidence}%),请提供更清晰的描述。\n` + `解析结果:${intent.description}`, "LOW_CONFIDENCE", ); } // 如果是预览模式(默认) if (!p.confirm) { return json({ ok: true, preview: true, parsed_intent: intent, message: "请确认以下解析结果是否正确。如果正确,请使用 confirm=true 参数确认记账。", suggestions: [ intent.tax_note ? `💡 税务提示:${intent.tax_note}` : null, intent.is_deductible ? "✅ 此笔支出可抵税,记得保存发票" : null, intent.amount > 2000 && intent.category === "fixed_asset" ? "📊 大额固定资产购置,可按年折旧" : null, ].filter(Boolean), }); } // 确认模式:创建交易记录 const transactionId = db.genId(); const now = new Date().toISOString(); db.execute( `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, transactionId, p.company_id, intent.type, intent.category, intent.amount, intent.description, intent.counterparty ?? "", intent.date, now, ); const transaction = db.queryOne("SELECT * FROM opc_transactions WHERE id = ?", transactionId); // 生成记账成功提示 const successMessages = [ `✅ 记账成功!${intent.type === "income" ? "收入" : "支出"} ${intent.amount} 元`, ]; if (intent.tax_note) { successMessages.push(`💡 ${intent.tax_note}`); } if (intent.is_deductible && intent.type === "expense") { successMessages.push("✅ 此笔支出可抵税,记得保存发票用于年度汇算清缴"); } // 检查月度预算(如果支出) if (intent.type === "expense") { const currentMonth = intent.date.slice(0, 7); const monthlyExpense = db.queryOne( `SELECT COALESCE(SUM(amount), 0) as total FROM opc_transactions WHERE company_id = ? AND type = 'expense' AND transaction_date LIKE ?`, p.company_id, currentMonth + "%", ) as { total: number }; const totalExpense = monthlyExpense?.total ?? 0; if (totalExpense > 10000) { successMessages.push(`⚠️ 本月支出已达 ${totalExpense.toLocaleString()} 元`); } } return json({ ok: true, transaction, intent, messages: successMessages, }); } case "suggest_category": { const suggestion = await suggestCategory(p.description, api); return json({ ok: true, suggestion, }); } default: return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION"); } } catch (err) { return toolError(err instanceof Error ? err.message : String(err), "EXECUTION_ERROR"); } }, }, { name: "opc_smart_accounting" }, ); api.logger.info("opc: 已注册 opc_smart_accounting 工具"); }