galaxy-opc-plugin 0.1.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.
Files changed (46) hide show
  1. package/README.md +166 -0
  2. package/index.ts +145 -0
  3. package/openclaw.plugin.json +17 -0
  4. package/package.json +47 -0
  5. package/skills/basic-crm/SKILL.md +81 -0
  6. package/skills/basic-finance/SKILL.md +100 -0
  7. package/skills/business-monitoring/SKILL.md +120 -0
  8. package/skills/company-lifecycle/SKILL.md +99 -0
  9. package/skills/company-registration/SKILL.md +80 -0
  10. package/skills/finance-tax/SKILL.md +150 -0
  11. package/skills/hr-assistant/SKILL.md +127 -0
  12. package/skills/investment-management/SKILL.md +101 -0
  13. package/skills/legal-assistant/SKILL.md +113 -0
  14. package/skills/media-ops/SKILL.md +101 -0
  15. package/skills/procurement-management/SKILL.md +91 -0
  16. package/skills/project-management/SKILL.md +125 -0
  17. package/src/api/companies.ts +193 -0
  18. package/src/api/dashboard.ts +25 -0
  19. package/src/api/routes.ts +14 -0
  20. package/src/db/index.ts +63 -0
  21. package/src/db/migrations.ts +67 -0
  22. package/src/db/schema.ts +518 -0
  23. package/src/db/sqlite-adapter.ts +366 -0
  24. package/src/opc/company-manager.ts +82 -0
  25. package/src/opc/context-injector.ts +186 -0
  26. package/src/opc/reminder-service.ts +289 -0
  27. package/src/opc/types.ts +330 -0
  28. package/src/opc/workspace-factory.ts +189 -0
  29. package/src/tools/acquisition-tool.ts +150 -0
  30. package/src/tools/asset-package-tool.ts +283 -0
  31. package/src/tools/finance-tool.ts +244 -0
  32. package/src/tools/hr-tool.ts +211 -0
  33. package/src/tools/investment-tool.ts +201 -0
  34. package/src/tools/legal-tool.ts +191 -0
  35. package/src/tools/lifecycle-tool.ts +251 -0
  36. package/src/tools/media-tool.ts +174 -0
  37. package/src/tools/monitoring-tool.ts +207 -0
  38. package/src/tools/opb-tool.ts +193 -0
  39. package/src/tools/opc-tool.ts +206 -0
  40. package/src/tools/procurement-tool.ts +191 -0
  41. package/src/tools/project-tool.ts +203 -0
  42. package/src/tools/schemas.ts +163 -0
  43. package/src/tools/staff-tool.ts +211 -0
  44. package/src/utils/tool-helper.ts +16 -0
  45. package/src/web/config-ui.ts +3501 -0
  46. package/src/web/landing-page.ts +269 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * 星环OPC中心 — opc_finance 财税管理工具
3
+ */
4
+
5
+ import { Type, type Static } from "@sinclair/typebox";
6
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
+ import type { OpcDatabase } from "../db/index.js";
8
+ import { json } from "../utils/tool-helper.js";
9
+
10
+ const FinanceSchema = Type.Union([
11
+ Type.Object({
12
+ action: Type.Literal("create_invoice"),
13
+ company_id: Type.String({ description: "公司 ID" }),
14
+ type: Type.String({ description: "发票类型: sales(销项) 或 purchase(进项)" }),
15
+ counterparty: Type.String({ description: "对方单位名称" }),
16
+ amount: Type.Number({ description: "不含税金额(元)" }),
17
+ tax_rate: Type.Optional(Type.Number({ description: "税率,如 0.06 表示 6%,默认 0.06" })),
18
+ invoice_number: Type.Optional(Type.String({ description: "发票号码" })),
19
+ issue_date: Type.Optional(Type.String({ description: "开票日期 (YYYY-MM-DD)" })),
20
+ notes: Type.Optional(Type.String({ description: "备注" })),
21
+ }),
22
+ Type.Object({
23
+ action: Type.Literal("list_invoices"),
24
+ company_id: Type.String({ description: "公司 ID" }),
25
+ type: Type.Optional(Type.String({ description: "按类型筛选: sales/purchase" })),
26
+ status: Type.Optional(Type.String({ description: "按状态筛选: draft/issued/paid/void" })),
27
+ }),
28
+ Type.Object({
29
+ action: Type.Literal("update_invoice_status"),
30
+ invoice_id: Type.String({ description: "发票 ID" }),
31
+ status: Type.String({ description: "新状态: issued/paid/void" }),
32
+ }),
33
+ Type.Object({
34
+ action: Type.Literal("calc_vat"),
35
+ company_id: Type.String({ description: "公司 ID" }),
36
+ period: Type.String({ description: "税期,如 2025-Q1 或 2025-01" }),
37
+ }),
38
+ Type.Object({
39
+ action: Type.Literal("calc_income_tax"),
40
+ company_id: Type.String({ description: "公司 ID" }),
41
+ period: Type.String({ description: "税期,如 2025-Q1 或 2025" }),
42
+ annual_revenue: Type.Optional(Type.Number({ description: "年收入(用于年度汇算)" })),
43
+ annual_cost: Type.Optional(Type.Number({ description: "年成本(用于年度汇算)" })),
44
+ }),
45
+ Type.Object({
46
+ action: Type.Literal("create_tax_filing"),
47
+ company_id: Type.String({ description: "公司 ID" }),
48
+ period: Type.String({ description: "税期" }),
49
+ tax_type: Type.String({ description: "税种: vat/income_tax/other" }),
50
+ revenue: Type.Number({ description: "营收" }),
51
+ deductible: Type.Number({ description: "可抵扣/成本" }),
52
+ tax_amount: Type.Number({ description: "应纳税额" }),
53
+ due_date: Type.Optional(Type.String({ description: "申报截止日期" })),
54
+ notes: Type.Optional(Type.String({ description: "备注" })),
55
+ }),
56
+ Type.Object({
57
+ action: Type.Literal("list_tax_filings"),
58
+ company_id: Type.String({ description: "公司 ID" }),
59
+ tax_type: Type.Optional(Type.String({ description: "按税种筛选" })),
60
+ }),
61
+ Type.Object({
62
+ action: Type.Literal("tax_calendar"),
63
+ company_id: Type.String({ description: "公司 ID" }),
64
+ }),
65
+ Type.Object({
66
+ action: Type.Literal("delete_invoice"),
67
+ invoice_id: Type.String({ description: "发票 ID" }),
68
+ }),
69
+ Type.Object({
70
+ action: Type.Literal("delete_tax_filing"),
71
+ filing_id: Type.String({ description: "税务申报 ID" }),
72
+ }),
73
+ ]);
74
+
75
+ type FinanceParams = Static<typeof FinanceSchema>;
76
+
77
+ /** 小规模纳税人增值税简易计算 */
78
+ function calcVatSimple(salesAmount: number, rate = 0.03): { tax: number; rate: number } {
79
+ return { tax: Math.round(salesAmount * rate * 100) / 100, rate };
80
+ }
81
+
82
+ /** 企业所得税简算(小型微利企业优惠) */
83
+ function calcIncomeTax(profit: number): { tax: number; rate: number; note: string } {
84
+ if (profit <= 0) return { tax: 0, rate: 0, note: "无应纳税所得额" };
85
+ if (profit <= 3_000_000) {
86
+ // 小型微利企业: 应纳税所得额 ≤ 300万,实际税负 5%
87
+ const tax = Math.round(profit * 0.05 * 100) / 100;
88
+ return { tax, rate: 0.05, note: "小型微利企业优惠税率 5%" };
89
+ }
90
+ const tax = Math.round(profit * 0.25 * 100) / 100;
91
+ return { tax, rate: 0.25, note: "一般企业税率 25%" };
92
+ }
93
+
94
+ export function registerFinanceTool(api: OpenClawPluginApi, db: OpcDatabase): void {
95
+ api.registerTool(
96
+ {
97
+ name: "opc_finance",
98
+ label: "OPC 财税管理",
99
+ description:
100
+ "财税管理工具。操作: create_invoice(创建发票), list_invoices(发票列表), " +
101
+ "update_invoice_status(更新发票状态), calc_vat(增值税计算), " +
102
+ "calc_income_tax(所得税计算), create_tax_filing(创建税务申报), " +
103
+ "list_tax_filings(申报列表), tax_calendar(税务日历), delete_invoice(删除发票), delete_tax_filing(删除税务申报记录)",
104
+ parameters: FinanceSchema,
105
+ async execute(_toolCallId, params) {
106
+ const p = params as FinanceParams;
107
+ try {
108
+ switch (p.action) {
109
+ case "create_invoice": {
110
+ const id = db.genId();
111
+ const now = new Date().toISOString();
112
+ const taxRate = p.tax_rate ?? 0.06;
113
+ const taxAmount = Math.round(p.amount * taxRate * 100) / 100;
114
+ const totalAmount = p.amount + taxAmount;
115
+ db.execute(
116
+ `INSERT INTO opc_invoices (id, company_id, invoice_number, type, counterparty, amount, tax_rate, tax_amount, total_amount, status, issue_date, notes, created_at)
117
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'draft', ?, ?, ?)`,
118
+ id, p.company_id, p.invoice_number ?? "", p.type, p.counterparty,
119
+ p.amount, taxRate, taxAmount, totalAmount,
120
+ p.issue_date ?? now.slice(0, 10), p.notes ?? "", now,
121
+ );
122
+ return json(db.queryOne("SELECT * FROM opc_invoices WHERE id = ?", id));
123
+ }
124
+
125
+ case "list_invoices": {
126
+ let sql = "SELECT * FROM opc_invoices WHERE company_id = ?";
127
+ const params2: unknown[] = [p.company_id];
128
+ if (p.type) { sql += " AND type = ?"; params2.push(p.type); }
129
+ if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
130
+ sql += " ORDER BY created_at DESC";
131
+ return json(db.query(sql, ...params2));
132
+ }
133
+
134
+ case "update_invoice_status": {
135
+ db.execute("UPDATE opc_invoices SET status = ? WHERE id = ?", p.status, p.invoice_id);
136
+ return json(db.queryOne("SELECT * FROM opc_invoices WHERE id = ?", p.invoice_id) ?? { error: "发票不存在" });
137
+ }
138
+
139
+ case "calc_vat": {
140
+ const sales = db.query(
141
+ "SELECT COALESCE(SUM(amount), 0) as total FROM opc_invoices WHERE company_id = ? AND type = 'sales' AND issue_date LIKE ?",
142
+ p.company_id, p.period + "%",
143
+ ) as { total: number }[];
144
+ const purchases = db.query(
145
+ "SELECT COALESCE(SUM(tax_amount), 0) as total FROM opc_invoices WHERE company_id = ? AND type = 'purchase' AND issue_date LIKE ?",
146
+ p.company_id, p.period + "%",
147
+ ) as { total: number }[];
148
+ const salesTotal = sales[0]?.total ?? 0;
149
+ const inputTax = purchases[0]?.total ?? 0;
150
+ const vat = calcVatSimple(salesTotal);
151
+ return json({
152
+ period: p.period,
153
+ sales_amount: salesTotal,
154
+ output_tax: vat.tax,
155
+ input_tax: inputTax,
156
+ payable: Math.max(0, vat.tax - inputTax),
157
+ note: "小规模纳税人简易计算,税率 3%",
158
+ });
159
+ }
160
+
161
+ case "calc_income_tax": {
162
+ let revenue = p.annual_revenue;
163
+ let cost = p.annual_cost;
164
+ if (revenue === undefined || cost === undefined) {
165
+ const summary = db.getFinanceSummary(p.company_id);
166
+ revenue = revenue ?? summary.total_income;
167
+ cost = cost ?? summary.total_expense;
168
+ }
169
+ const profit = revenue - cost;
170
+ const result = calcIncomeTax(profit);
171
+ return json({
172
+ period: p.period,
173
+ revenue,
174
+ cost,
175
+ profit,
176
+ ...result,
177
+ });
178
+ }
179
+
180
+ case "create_tax_filing": {
181
+ const id = db.genId();
182
+ const now = new Date().toISOString();
183
+ db.execute(
184
+ `INSERT INTO opc_tax_filings (id, company_id, period, tax_type, revenue, deductible, tax_amount, status, due_date, notes, created_at)
185
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
186
+ id, p.company_id, p.period, p.tax_type,
187
+ p.revenue, p.deductible, p.tax_amount,
188
+ p.due_date ?? "", p.notes ?? "", now,
189
+ );
190
+ return json(db.queryOne("SELECT * FROM opc_tax_filings WHERE id = ?", id));
191
+ }
192
+
193
+ case "list_tax_filings": {
194
+ let sql = "SELECT * FROM opc_tax_filings WHERE company_id = ?";
195
+ const params2: unknown[] = [p.company_id];
196
+ if (p.tax_type) { sql += " AND tax_type = ?"; params2.push(p.tax_type); }
197
+ sql += " ORDER BY period DESC";
198
+ return json(db.query(sql, ...params2));
199
+ }
200
+
201
+ case "tax_calendar":
202
+ return json({
203
+ monthly: [
204
+ { deadline: "每月15日", item: "增值税申报(小规模按季)" },
205
+ { deadline: "每月15日", item: "个人所得税代扣代缴" },
206
+ { deadline: "每月15日", item: "城建税/教育费附加" },
207
+ ],
208
+ quarterly: [
209
+ { deadline: "季后15日内", item: "企业所得税预缴" },
210
+ { deadline: "季后15日内", item: "增值税申报(小规模纳税人)" },
211
+ ],
212
+ annual: [
213
+ { deadline: "次年5月31日前", item: "企业所得税汇算清缴" },
214
+ { deadline: "次年6月30日前", item: "工商年报公示" },
215
+ ],
216
+ pending: db.query(
217
+ "SELECT * FROM opc_tax_filings WHERE company_id = ? AND status = 'pending' ORDER BY due_date",
218
+ p.company_id,
219
+ ),
220
+ });
221
+
222
+ case "delete_invoice": {
223
+ db.execute("DELETE FROM opc_invoices WHERE id = ?", p.invoice_id);
224
+ return json({ ok: true });
225
+ }
226
+
227
+ case "delete_tax_filing": {
228
+ db.execute("DELETE FROM opc_tax_filings WHERE id = ?", p.filing_id);
229
+ return json({ ok: true });
230
+ }
231
+
232
+ default:
233
+ return json({ error: `未知操作: ${(p as { action: string }).action}` });
234
+ }
235
+ } catch (err) {
236
+ return json({ error: err instanceof Error ? err.message : String(err) });
237
+ }
238
+ },
239
+ },
240
+ { name: "opc_finance" },
241
+ );
242
+
243
+ api.logger.info("opc: 已注册 opc_finance 工具");
244
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * 星环OPC中心 — opc_hr 人力资源工具
3
+ */
4
+
5
+ import { Type, type Static } from "@sinclair/typebox";
6
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
+ import type { OpcDatabase } from "../db/index.js";
8
+ import { json } from "../utils/tool-helper.js";
9
+
10
+ const HrSchema = Type.Union([
11
+ Type.Object({
12
+ action: Type.Literal("add_employee"),
13
+ company_id: Type.String({ description: "公司 ID" }),
14
+ employee_name: Type.String({ description: "员工姓名" }),
15
+ position: Type.String({ description: "岗位" }),
16
+ salary: Type.Number({ description: "月薪(元)" }),
17
+ contract_type: Type.Optional(Type.String({ description: "用工类型: full_time/part_time/contractor/intern" })),
18
+ start_date: Type.Optional(Type.String({ description: "入职日期 (YYYY-MM-DD)" })),
19
+ notes: Type.Optional(Type.String({ description: "备注" })),
20
+ }),
21
+ Type.Object({
22
+ action: Type.Literal("list_employees"),
23
+ company_id: Type.String({ description: "公司 ID" }),
24
+ status: Type.Optional(Type.String({ description: "按状态筛选: active/resigned/terminated" })),
25
+ }),
26
+ Type.Object({
27
+ action: Type.Literal("update_employee"),
28
+ record_id: Type.String({ description: "员工记录 ID" }),
29
+ salary: Type.Optional(Type.Number({ description: "新月薪" })),
30
+ position: Type.Optional(Type.String({ description: "新岗位" })),
31
+ status: Type.Optional(Type.String({ description: "新状态: active/resigned/terminated" })),
32
+ end_date: Type.Optional(Type.String({ description: "离职日期" })),
33
+ notes: Type.Optional(Type.String({ description: "备注" })),
34
+ }),
35
+ Type.Object({
36
+ action: Type.Literal("calc_social_insurance"),
37
+ salary: Type.Number({ description: "月薪基数(元)" }),
38
+ city: Type.Optional(Type.String({ description: "城市,默认按一般标准" })),
39
+ }),
40
+ Type.Object({
41
+ action: Type.Literal("calc_personal_tax"),
42
+ monthly_salary: Type.Number({ description: "月薪(元)" }),
43
+ social_insurance: Type.Optional(Type.Number({ description: "个人社保公积金合计(元)" })),
44
+ special_deduction: Type.Optional(Type.Number({ description: "专项附加扣除合计(元/月)" })),
45
+ }),
46
+ Type.Object({
47
+ action: Type.Literal("payroll_summary"),
48
+ company_id: Type.String({ description: "公司 ID" }),
49
+ }),
50
+ Type.Object({
51
+ action: Type.Literal("delete_employee"),
52
+ record_id: Type.String({ description: "员工记录 ID" }),
53
+ }),
54
+ ]);
55
+
56
+ type HrParams = Static<typeof HrSchema>;
57
+
58
+ /** 社保公积金估算(一般城市标准) */
59
+ function calcSocialInsurance(salary: number) {
60
+ const base = Math.max(Math.min(salary, 31884), 6377); // 一般上下限
61
+ const company = {
62
+ pension: Math.round(base * 0.16 * 100) / 100,
63
+ medical: Math.round(base * 0.095 * 100) / 100,
64
+ unemployment: Math.round(base * 0.005 * 100) / 100,
65
+ injury: Math.round(base * 0.004 * 100) / 100,
66
+ housing_fund: Math.round(base * 0.12 * 100) / 100,
67
+ };
68
+ const personal = {
69
+ pension: Math.round(base * 0.08 * 100) / 100,
70
+ medical: Math.round(base * 0.02 * 100) / 100,
71
+ unemployment: Math.round(base * 0.005 * 100) / 100,
72
+ housing_fund: Math.round(base * 0.12 * 100) / 100,
73
+ };
74
+ return {
75
+ base,
76
+ company_total: Object.values(company).reduce((a, b) => a + b, 0),
77
+ personal_total: Object.values(personal).reduce((a, b) => a + b, 0),
78
+ company,
79
+ personal,
80
+ };
81
+ }
82
+
83
+ /** 个税累进税率计算 */
84
+ function calcPersonalTax(monthlyTaxable: number): { tax: number; rate: number; deduction: number } {
85
+ const annual = monthlyTaxable * 12;
86
+ const brackets = [
87
+ { limit: 36000, rate: 0.03, deduction: 0 },
88
+ { limit: 144000, rate: 0.10, deduction: 2520 },
89
+ { limit: 300000, rate: 0.20, deduction: 16920 },
90
+ { limit: 420000, rate: 0.25, deduction: 31920 },
91
+ { limit: 660000, rate: 0.30, deduction: 52920 },
92
+ { limit: 960000, rate: 0.35, deduction: 85920 },
93
+ { limit: Infinity, rate: 0.45, deduction: 181920 },
94
+ ];
95
+ for (const b of brackets) {
96
+ if (annual <= b.limit) {
97
+ const annualTax = Math.round((annual * b.rate - b.deduction) * 100) / 100;
98
+ return { tax: Math.round(annualTax / 12 * 100) / 100, rate: b.rate, deduction: b.deduction };
99
+ }
100
+ }
101
+ return { tax: 0, rate: 0, deduction: 0 };
102
+ }
103
+
104
+ export function registerHrTool(api: OpenClawPluginApi, db: OpcDatabase): void {
105
+ api.registerTool(
106
+ {
107
+ name: "opc_hr",
108
+ label: "OPC 人力资源",
109
+ description:
110
+ "人力资源管理工具。操作: add_employee(添加员工), list_employees(员工列表), " +
111
+ "update_employee(更新员工), calc_social_insurance(社保公积金计算), " +
112
+ "calc_personal_tax(个税计算), payroll_summary(薪酬汇总), delete_employee(删除员工记录)",
113
+ parameters: HrSchema,
114
+ async execute(_toolCallId, params) {
115
+ const p = params as HrParams;
116
+ try {
117
+ switch (p.action) {
118
+ case "add_employee": {
119
+ const id = db.genId();
120
+ const now = new Date().toISOString();
121
+ const si = calcSocialInsurance(p.salary);
122
+ db.execute(
123
+ `INSERT INTO opc_hr_records (id, company_id, employee_name, position, salary, social_insurance, housing_fund, start_date, contract_type, status, notes, created_at, updated_at)
124
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?)`,
125
+ id, p.company_id, p.employee_name, p.position, p.salary,
126
+ si.personal.pension + si.personal.medical + si.personal.unemployment,
127
+ si.personal.housing_fund,
128
+ p.start_date ?? now.slice(0, 10),
129
+ p.contract_type ?? "full_time",
130
+ p.notes ?? "", now, now,
131
+ );
132
+ return json(db.queryOne("SELECT * FROM opc_hr_records WHERE id = ?", id));
133
+ }
134
+
135
+ case "list_employees": {
136
+ let sql = "SELECT * FROM opc_hr_records WHERE company_id = ?";
137
+ const params2: unknown[] = [p.company_id];
138
+ if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
139
+ sql += " ORDER BY created_at DESC";
140
+ return json(db.query(sql, ...params2));
141
+ }
142
+
143
+ case "update_employee": {
144
+ const fields: string[] = [];
145
+ const values: unknown[] = [];
146
+ if (p.salary !== undefined) { fields.push("salary = ?"); values.push(p.salary); }
147
+ if (p.position) { fields.push("position = ?"); values.push(p.position); }
148
+ if (p.status) { fields.push("status = ?"); values.push(p.status); }
149
+ if (p.end_date) { fields.push("end_date = ?"); values.push(p.end_date); }
150
+ if (p.notes) { fields.push("notes = ?"); values.push(p.notes); }
151
+ fields.push("updated_at = ?"); values.push(new Date().toISOString());
152
+ values.push(p.record_id);
153
+ db.execute(`UPDATE opc_hr_records SET ${fields.join(", ")} WHERE id = ?`, ...values);
154
+ return json(db.queryOne("SELECT * FROM opc_hr_records WHERE id = ?", p.record_id) ?? { error: "记录不存在" });
155
+ }
156
+
157
+ case "calc_social_insurance":
158
+ return json(calcSocialInsurance(p.salary));
159
+
160
+ case "calc_personal_tax": {
161
+ const si = p.social_insurance ?? calcSocialInsurance(p.monthly_salary).personal_total;
162
+ const special = p.special_deduction ?? 0;
163
+ const taxable = Math.max(0, p.monthly_salary - 5000 - si - special);
164
+ const result = calcPersonalTax(taxable);
165
+ return json({
166
+ monthly_salary: p.monthly_salary,
167
+ threshold: 5000,
168
+ social_insurance_deduction: si,
169
+ special_deduction: special,
170
+ taxable_income: taxable,
171
+ monthly_tax: result.tax,
172
+ rate: `${result.rate * 100}%`,
173
+ take_home: Math.round((p.monthly_salary - si - result.tax) * 100) / 100,
174
+ });
175
+ }
176
+
177
+ case "payroll_summary": {
178
+ const employees = db.query(
179
+ "SELECT * FROM opc_hr_records WHERE company_id = ? AND status = 'active'",
180
+ p.company_id,
181
+ ) as { salary: number; social_insurance: number; housing_fund: number }[];
182
+ const totalSalary = employees.reduce((s, e) => s + e.salary, 0);
183
+ const totalSI = employees.reduce((s, e) => s + e.social_insurance, 0);
184
+ const totalHF = employees.reduce((s, e) => s + e.housing_fund, 0);
185
+ return json({
186
+ active_count: employees.length,
187
+ total_salary: totalSalary,
188
+ total_social_insurance: totalSI,
189
+ total_housing_fund: totalHF,
190
+ total_cost: totalSalary + totalSI + totalHF,
191
+ });
192
+ }
193
+
194
+ case "delete_employee": {
195
+ db.execute("DELETE FROM opc_hr_records WHERE id = ?", p.record_id);
196
+ return json({ ok: true });
197
+ }
198
+
199
+ default:
200
+ return json({ error: `未知操作: ${(p as { action: string }).action}` });
201
+ }
202
+ } catch (err) {
203
+ return json({ error: err instanceof Error ? err.message : String(err) });
204
+ }
205
+ },
206
+ },
207
+ { name: "opc_hr" },
208
+ );
209
+
210
+ api.logger.info("opc: 已注册 opc_hr 工具");
211
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * 星环OPC中心 — opc_investment 投融资管理工具
3
+ */
4
+
5
+ import { Type, type Static } from "@sinclair/typebox";
6
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
+ import type { OpcDatabase } from "../db/index.js";
8
+ import { json } from "../utils/tool-helper.js";
9
+
10
+ const InvestmentSchema = Type.Union([
11
+ Type.Object({
12
+ action: Type.Literal("create_round"),
13
+ company_id: Type.String({ description: "公司 ID" }),
14
+ round_name: Type.String({ description: "轮次名称: seed/angel/pre-A/A/B/C/D/IPO 等" }),
15
+ amount: Type.Number({ description: "融资金额(元)" }),
16
+ valuation_pre: Type.Optional(Type.Number({ description: "投前估值(元)" })),
17
+ valuation_post: Type.Optional(Type.Number({ description: "投后估值(元)" })),
18
+ lead_investor: Type.Optional(Type.String({ description: "领投方" })),
19
+ close_date: Type.Optional(Type.String({ description: "关闭日期 (YYYY-MM-DD)" })),
20
+ notes: Type.Optional(Type.String({ description: "备注" })),
21
+ }),
22
+ Type.Object({
23
+ action: Type.Literal("list_rounds"),
24
+ company_id: Type.String({ description: "公司 ID" }),
25
+ status: Type.Optional(Type.String({ description: "按状态筛选: planning/fundraising/closed/cancelled" })),
26
+ }),
27
+ Type.Object({
28
+ action: Type.Literal("add_investor"),
29
+ company_id: Type.String({ description: "公司 ID" }),
30
+ round_id: Type.String({ description: "轮次 ID" }),
31
+ name: Type.String({ description: "投资人名称" }),
32
+ type: Type.Optional(Type.String({ description: "投资人类型: individual/institutional/angel/vc/strategic" })),
33
+ amount: Type.Number({ description: "投资金额(元)" }),
34
+ equity_percent: Type.Optional(Type.Number({ description: "持股比例(%)" })),
35
+ contact: Type.Optional(Type.String({ description: "联系方式" })),
36
+ notes: Type.Optional(Type.String({ description: "备注" })),
37
+ }),
38
+ Type.Object({
39
+ action: Type.Literal("list_investors"),
40
+ company_id: Type.String({ description: "公司 ID" }),
41
+ round_id: Type.Optional(Type.String({ description: "按轮次筛选" })),
42
+ }),
43
+ Type.Object({
44
+ action: Type.Literal("cap_table"),
45
+ company_id: Type.String({ description: "公司 ID" }),
46
+ }),
47
+ Type.Object({
48
+ action: Type.Literal("update_round"),
49
+ round_id: Type.String({ description: "轮次 ID" }),
50
+ status: Type.Optional(Type.String({ description: "新状态: planning/fundraising/closed/cancelled" })),
51
+ amount: Type.Optional(Type.Number({ description: "实际融资金额(元)" })),
52
+ valuation_post: Type.Optional(Type.Number({ description: "投后估值(元)" })),
53
+ lead_investor: Type.Optional(Type.String({ description: "领投方" })),
54
+ close_date: Type.Optional(Type.String({ description: "关闭日期 (YYYY-MM-DD)" })),
55
+ notes: Type.Optional(Type.String({ description: "备注" })),
56
+ }),
57
+ Type.Object({
58
+ action: Type.Literal("valuation_history"),
59
+ company_id: Type.String({ description: "公司 ID" }),
60
+ }),
61
+ Type.Object({
62
+ action: Type.Literal("delete_round"),
63
+ round_id: Type.String({ description: "融资轮次 ID" }),
64
+ }),
65
+ Type.Object({
66
+ action: Type.Literal("delete_investor"),
67
+ investor_id: Type.String({ description: "投资人 ID" }),
68
+ }),
69
+ ]);
70
+
71
+ type InvestmentParams = Static<typeof InvestmentSchema>;
72
+
73
+ export function registerInvestmentTool(api: OpenClawPluginApi, db: OpcDatabase): void {
74
+ api.registerTool(
75
+ {
76
+ name: "opc_investment",
77
+ label: "OPC 投融资管理",
78
+ description:
79
+ "投融资管理工具。操作: create_round(创建融资轮次), update_round(更新轮次状态/关闭融资), list_rounds(轮次列表), " +
80
+ "add_investor(添加投资人), list_investors(投资人列表), " +
81
+ "cap_table(股权结构表), valuation_history(估值变化历史), delete_round(删除融资轮次及投资人), delete_investor(删除投资人)",
82
+ parameters: InvestmentSchema,
83
+ async execute(_toolCallId, params) {
84
+ const p = params as InvestmentParams;
85
+ try {
86
+ switch (p.action) {
87
+ case "create_round": {
88
+ const id = db.genId();
89
+ const now = new Date().toISOString();
90
+ const valuationPost = p.valuation_post ?? (p.valuation_pre ? p.valuation_pre + p.amount : 0);
91
+ db.execute(
92
+ `INSERT INTO opc_investment_rounds (id, company_id, round_name, amount, valuation_pre, valuation_post, status, lead_investor, close_date, notes, created_at)
93
+ VALUES (?, ?, ?, ?, ?, ?, 'planning', ?, ?, ?, ?)`,
94
+ id, p.company_id, p.round_name, p.amount,
95
+ p.valuation_pre ?? 0, valuationPost,
96
+ p.lead_investor ?? "", p.close_date ?? "", p.notes ?? "", now,
97
+ );
98
+ return json(db.queryOne("SELECT * FROM opc_investment_rounds WHERE id = ?", id));
99
+ }
100
+
101
+ case "list_rounds": {
102
+ let sql = "SELECT * FROM opc_investment_rounds WHERE company_id = ?";
103
+ const params2: unknown[] = [p.company_id];
104
+ if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
105
+ sql += " ORDER BY created_at DESC";
106
+ return json(db.query(sql, ...params2));
107
+ }
108
+
109
+ case "add_investor": {
110
+ const id = db.genId();
111
+ const now = new Date().toISOString();
112
+ db.execute(
113
+ `INSERT INTO opc_investors (id, round_id, company_id, name, type, amount, equity_percent, contact, notes, created_at)
114
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
115
+ id, p.round_id, p.company_id, p.name,
116
+ p.type ?? "individual", p.amount,
117
+ p.equity_percent ?? 0, p.contact ?? "", p.notes ?? "", now,
118
+ );
119
+ return json(db.queryOne("SELECT * FROM opc_investors WHERE id = ?", id));
120
+ }
121
+
122
+ case "list_investors": {
123
+ let sql = "SELECT * FROM opc_investors WHERE company_id = ?";
124
+ const params2: unknown[] = [p.company_id];
125
+ if (p.round_id) { sql += " AND round_id = ?"; params2.push(p.round_id); }
126
+ sql += " ORDER BY created_at DESC";
127
+ return json(db.query(sql, ...params2));
128
+ }
129
+
130
+ case "cap_table": {
131
+ const investors = db.query(
132
+ `SELECT i.name, i.type, i.amount, i.equity_percent, r.round_name
133
+ FROM opc_investors i
134
+ JOIN opc_investment_rounds r ON i.round_id = r.id
135
+ WHERE i.company_id = ?
136
+ ORDER BY r.created_at, i.created_at`,
137
+ p.company_id,
138
+ ) as { name: string; type: string; amount: number; equity_percent: number; round_name: string }[];
139
+
140
+ const totalEquity = investors.reduce((sum, inv) => sum + inv.equity_percent, 0);
141
+ const totalInvested = investors.reduce((sum, inv) => sum + inv.amount, 0);
142
+
143
+ return json({
144
+ investors,
145
+ total_invested: totalInvested,
146
+ total_investor_equity: totalEquity,
147
+ founder_equity: Math.max(0, 100 - totalEquity),
148
+ investor_count: investors.length,
149
+ });
150
+ }
151
+
152
+ case "update_round": {
153
+ const sets: string[] = [];
154
+ const vals: unknown[] = [];
155
+ if (p.status !== undefined) { sets.push("status = ?"); vals.push(p.status); }
156
+ if (p.amount !== undefined) { sets.push("amount = ?"); vals.push(p.amount); }
157
+ if (p.valuation_post !== undefined) { sets.push("valuation_post = ?"); vals.push(p.valuation_post); }
158
+ if (p.lead_investor !== undefined) { sets.push("lead_investor = ?"); vals.push(p.lead_investor); }
159
+ if (p.close_date !== undefined) { sets.push("close_date = ?"); vals.push(p.close_date); }
160
+ if (p.notes !== undefined) { sets.push("notes = ?"); vals.push(p.notes); }
161
+ if (sets.length === 0) return json({ error: "未提供任何更新字段" });
162
+ vals.push(p.round_id);
163
+ db.execute(`UPDATE opc_investment_rounds SET ${sets.join(", ")} WHERE id = ?`, ...vals);
164
+ return json(db.queryOne("SELECT * FROM opc_investment_rounds WHERE id = ?", p.round_id));
165
+ }
166
+
167
+ case "valuation_history": {
168
+ const rounds = db.query(
169
+ `SELECT round_name, amount, valuation_pre, valuation_post, status, close_date, created_at
170
+ FROM opc_investment_rounds
171
+ WHERE company_id = ?
172
+ ORDER BY created_at ASC`,
173
+ p.company_id,
174
+ );
175
+ return json({ history: rounds });
176
+ }
177
+
178
+ case "delete_round": {
179
+ db.execute("DELETE FROM opc_investors WHERE round_id = ?", p.round_id);
180
+ db.execute("DELETE FROM opc_investment_rounds WHERE id = ?", p.round_id);
181
+ return json({ ok: true });
182
+ }
183
+
184
+ case "delete_investor": {
185
+ db.execute("DELETE FROM opc_investors WHERE id = ?", p.investor_id);
186
+ return json({ ok: true });
187
+ }
188
+
189
+ default:
190
+ return json({ error: `未知操作: ${(p as { action: string }).action}` });
191
+ }
192
+ } catch (err) {
193
+ return json({ error: err instanceof Error ? err.message : String(err) });
194
+ }
195
+ },
196
+ },
197
+ { name: "opc_investment" },
198
+ );
199
+
200
+ api.logger.info("opc: 已注册 opc_investment 工具");
201
+ }