galaxy-opc-plugin 0.1.1 → 0.2.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.
@@ -0,0 +1,153 @@
1
+ /**
2
+ * 星环OPC中心 — hr-tool 核心计算函数单元测试
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+
7
+ // 复制 hr-tool.ts 中的纯计算函数进行测试
8
+
9
+ /** 社保公积金估算(一般城市标准) */
10
+ function calcSocialInsurance(salary: number) {
11
+ const base = Math.max(Math.min(salary, 31884), 6377); // 一般上下限
12
+ const company = {
13
+ pension: Math.round(base * 0.16 * 100) / 100,
14
+ medical: Math.round(base * 0.095 * 100) / 100,
15
+ unemployment: Math.round(base * 0.005 * 100) / 100,
16
+ injury: Math.round(base * 0.004 * 100) / 100,
17
+ housing_fund: Math.round(base * 0.12 * 100) / 100,
18
+ };
19
+ const personal = {
20
+ pension: Math.round(base * 0.08 * 100) / 100,
21
+ medical: Math.round(base * 0.02 * 100) / 100,
22
+ unemployment: Math.round(base * 0.005 * 100) / 100,
23
+ housing_fund: Math.round(base * 0.12 * 100) / 100,
24
+ };
25
+ return {
26
+ base,
27
+ company_total: Object.values(company).reduce((a, b) => a + b, 0),
28
+ personal_total: Object.values(personal).reduce((a, b) => a + b, 0),
29
+ company,
30
+ personal,
31
+ };
32
+ }
33
+
34
+ /** 个税累进税率计算 */
35
+ function calcPersonalTax(monthlyTaxable: number): { tax: number; rate: number; deduction: number } {
36
+ const annual = monthlyTaxable * 12;
37
+ const brackets = [
38
+ { limit: 36000, rate: 0.03, deduction: 0 },
39
+ { limit: 144000, rate: 0.10, deduction: 2520 },
40
+ { limit: 300000, rate: 0.20, deduction: 16920 },
41
+ { limit: 420000, rate: 0.25, deduction: 31920 },
42
+ { limit: 660000, rate: 0.30, deduction: 52920 },
43
+ { limit: 960000, rate: 0.35, deduction: 85920 },
44
+ { limit: Infinity, rate: 0.45, deduction: 181920 },
45
+ ];
46
+ for (const b of brackets) {
47
+ if (annual <= b.limit) {
48
+ const annualTax = Math.round((annual * b.rate - b.deduction) * 100) / 100;
49
+ return { tax: Math.round(annualTax / 12 * 100) / 100, rate: b.rate, deduction: b.deduction };
50
+ }
51
+ }
52
+ return { tax: 0, rate: 0, deduction: 0 };
53
+ }
54
+
55
+ describe("hr-tool calculations", () => {
56
+ describe("calcSocialInsurance — 社保公积金计算", () => {
57
+ it("should calculate social insurance for normal salary", () => {
58
+ const result = calcSocialInsurance(10000);
59
+ expect(result.base).toBe(10000);
60
+
61
+ // 公司部分
62
+ expect(result.company.pension).toBe(1600); // 10000 × 16%
63
+ expect(result.company.medical).toBe(950); // 10000 × 9.5%
64
+ expect(result.company.unemployment).toBe(50); // 10000 × 0.5%
65
+ expect(result.company.injury).toBe(40); // 10000 × 0.4%
66
+ expect(result.company.housing_fund).toBe(1200); // 10000 × 12%
67
+
68
+ // 个人部分
69
+ expect(result.personal.pension).toBe(800); // 10000 × 8%
70
+ expect(result.personal.medical).toBe(200); // 10000 × 2%
71
+ expect(result.personal.unemployment).toBe(50); // 10000 × 0.5%
72
+ expect(result.personal.housing_fund).toBe(1200); // 10000 × 12%
73
+ });
74
+
75
+ it("should apply lower bound for low salary", () => {
76
+ const result = calcSocialInsurance(3000);
77
+ expect(result.base).toBe(6377); // 下限
78
+ expect(result.personal.pension).toBe(Math.round(6377 * 0.08 * 100) / 100);
79
+ });
80
+
81
+ it("should apply upper bound for high salary", () => {
82
+ const result = calcSocialInsurance(50000);
83
+ expect(result.base).toBe(31884); // 上限
84
+ expect(result.company.pension).toBe(Math.round(31884 * 0.16 * 100) / 100);
85
+ });
86
+
87
+ it("should compute correct totals", () => {
88
+ const result = calcSocialInsurance(10000);
89
+ const expectedCompanyTotal = 1600 + 950 + 50 + 40 + 1200;
90
+ const expectedPersonalTotal = 800 + 200 + 50 + 1200;
91
+ expect(result.company_total).toBeCloseTo(expectedCompanyTotal, 2);
92
+ expect(result.personal_total).toBeCloseTo(expectedPersonalTotal, 2);
93
+ });
94
+
95
+ it("should handle salary at lower boundary", () => {
96
+ const result = calcSocialInsurance(6377);
97
+ expect(result.base).toBe(6377);
98
+ });
99
+
100
+ it("should handle salary at upper boundary", () => {
101
+ const result = calcSocialInsurance(31884);
102
+ expect(result.base).toBe(31884);
103
+ });
104
+ });
105
+
106
+ describe("calcPersonalTax — 个税计算", () => {
107
+ it("should calculate tax at 3% bracket", () => {
108
+ // 月应纳税所得额 2000,年 24000,3% 税率
109
+ const result = calcPersonalTax(2000);
110
+ expect(result.rate).toBe(0.03);
111
+ expect(result.deduction).toBe(0);
112
+ expect(result.tax).toBe(60); // 2000 × 3%
113
+ });
114
+
115
+ it("should calculate tax at 10% bracket", () => {
116
+ // 月应纳税所得额 5000,年 60000,10% 税率
117
+ const result = calcPersonalTax(5000);
118
+ expect(result.rate).toBe(0.10);
119
+ expect(result.deduction).toBe(2520);
120
+ // (60000 × 0.10 - 2520) / 12 = (6000 - 2520) / 12 = 290
121
+ expect(result.tax).toBe(290);
122
+ });
123
+
124
+ it("should calculate tax at 20% bracket", () => {
125
+ // 月应纳税所得额 15000,年 180000
126
+ const result = calcPersonalTax(15000);
127
+ expect(result.rate).toBe(0.20);
128
+ // (180000 × 0.20 - 16920) / 12 = (36000 - 16920) / 12 = 1590
129
+ expect(result.tax).toBe(1590);
130
+ });
131
+
132
+ it("should return 0 for zero taxable income", () => {
133
+ const result = calcPersonalTax(0);
134
+ expect(result.tax).toBe(0);
135
+ expect(result.rate).toBe(0.03);
136
+ });
137
+
138
+ it("should handle boundary at 3000/month (36000/year)", () => {
139
+ const result = calcPersonalTax(3000);
140
+ // annual = 36000, falls in first bracket (limit: 36000)
141
+ expect(result.rate).toBe(0.03);
142
+ expect(result.tax).toBe(90); // 3000 × 3%
143
+ });
144
+
145
+ it("should handle high income at 45% bracket", () => {
146
+ // 月应纳税所得额 100000,年 1200000
147
+ const result = calcPersonalTax(100000);
148
+ expect(result.rate).toBe(0.45);
149
+ // (1200000 × 0.45 - 181920) / 12 = (540000 - 181920) / 12 = 29840
150
+ expect(result.tax).toBe(29840);
151
+ });
152
+ });
153
+ });
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const HrSchema = Type.Union([
11
11
  Type.Object({
@@ -151,7 +151,9 @@ export function registerHrTool(api: OpenClawPluginApi, db: OpcDatabase): void {
151
151
  fields.push("updated_at = ?"); values.push(new Date().toISOString());
152
152
  values.push(p.record_id);
153
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: "记录不存在" });
154
+ const hrRec = db.queryOne("SELECT * FROM opc_hr_records WHERE id = ?", p.record_id);
155
+ if (!hrRec) return toolError(`员工记录 ${p.record_id} 不存在`, "EMPLOYEE_NOT_FOUND");
156
+ return json(hrRec);
155
157
  }
156
158
 
157
159
  case "calc_social_insurance":
@@ -197,10 +199,10 @@ export function registerHrTool(api: OpenClawPluginApi, db: OpcDatabase): void {
197
199
  }
198
200
 
199
201
  default:
200
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
202
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
201
203
  }
202
204
  } catch (err) {
203
- return json({ error: err instanceof Error ? err.message : String(err) });
205
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
204
206
  }
205
207
  },
206
208
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const InvestmentSchema = Type.Union([
11
11
  Type.Object({
@@ -158,7 +158,7 @@ export function registerInvestmentTool(api: OpenClawPluginApi, db: OpcDatabase):
158
158
  if (p.lead_investor !== undefined) { sets.push("lead_investor = ?"); vals.push(p.lead_investor); }
159
159
  if (p.close_date !== undefined) { sets.push("close_date = ?"); vals.push(p.close_date); }
160
160
  if (p.notes !== undefined) { sets.push("notes = ?"); vals.push(p.notes); }
161
- if (sets.length === 0) return json({ error: "未提供任何更新字段" });
161
+ if (sets.length === 0) return toolError("未提供任何更新字段", "VALIDATION_ERROR");
162
162
  vals.push(p.round_id);
163
163
  db.execute(`UPDATE opc_investment_rounds SET ${sets.join(", ")} WHERE id = ?`, ...vals);
164
164
  return json(db.queryOne("SELECT * FROM opc_investment_rounds WHERE id = ?", p.round_id));
@@ -187,10 +187,10 @@ export function registerInvestmentTool(api: OpenClawPluginApi, db: OpcDatabase):
187
187
  }
188
188
 
189
189
  default:
190
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
190
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
191
191
  }
192
192
  } catch (err) {
193
- return json({ error: err instanceof Error ? err.message : String(err) });
193
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
194
194
  }
195
195
  },
196
196
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const LegalSchema = Type.Union([
11
11
  Type.Object({
@@ -120,8 +120,11 @@ export function registerLegalTool(api: OpenClawPluginApi, db: OpcDatabase): void
120
120
  return json(db.query(sql, ...params2));
121
121
  }
122
122
 
123
- case "get_contract":
124
- return json(db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", p.contract_id) ?? { error: "合同不存在" });
123
+ case "get_contract": {
124
+ const contract = db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", p.contract_id);
125
+ if (!contract) return toolError("合同不存在", "CONTRACT_NOT_FOUND");
126
+ return json(contract);
127
+ }
125
128
 
126
129
  case "update_contract": {
127
130
  const fields: string[] = [];
@@ -133,7 +136,9 @@ export function registerLegalTool(api: OpenClawPluginApi, db: OpcDatabase): void
133
136
  fields.push("updated_at = ?"); values.push(new Date().toISOString());
134
137
  values.push(p.contract_id);
135
138
  db.execute(`UPDATE opc_contracts SET ${fields.join(", ")} WHERE id = ?`, ...values);
136
- return json(db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", p.contract_id) ?? { error: "合同不存在" });
139
+ const updated = db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", p.contract_id);
140
+ if (!updated) return toolError("合同不存在", "CONTRACT_NOT_FOUND");
141
+ return json(updated);
137
142
  }
138
143
 
139
144
  case "contract_risk_check": {
@@ -166,7 +171,7 @@ export function registerLegalTool(api: OpenClawPluginApi, db: OpcDatabase): void
166
171
  case "contract_template": {
167
172
  const tpl = CONTRACT_TEMPLATES[p.contract_type];
168
173
  if (!tpl) {
169
- return json({ error: `无此模板,可用: ${Object.keys(CONTRACT_TEMPLATES).join(", ")}` });
174
+ return toolError(`无此模板,可用: ${Object.keys(CONTRACT_TEMPLATES).join(", ")}`, "INVALID_INPUT");
170
175
  }
171
176
  return json(tpl);
172
177
  }
@@ -177,10 +182,10 @@ export function registerLegalTool(api: OpenClawPluginApi, db: OpcDatabase): void
177
182
  }
178
183
 
179
184
  default:
180
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
185
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
181
186
  }
182
187
  } catch (err) {
183
- return json({ error: err instanceof Error ? err.message : String(err) });
188
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
184
189
  }
185
190
  },
186
191
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const LifecycleSchema = Type.Union([
11
11
  Type.Object({
@@ -151,7 +151,7 @@ export function registerLifecycleTool(api: OpenClawPluginApi, db: OpcDatabase):
151
151
  sets.push("completed_date = ?");
152
152
  vals.push(new Date().toISOString().slice(0, 10));
153
153
  }
154
- if (sets.length === 0) return json({ error: "未提供任何更新字段" });
154
+ if (sets.length === 0) return toolError("未提供任何更新字段", "VALIDATION_ERROR");
155
155
  vals.push(p.milestone_id);
156
156
  db.execute(`UPDATE opc_milestones SET ${sets.join(", ")} WHERE id = ?`, ...vals);
157
157
  return json(db.queryOne("SELECT * FROM opc_milestones WHERE id = ?", p.milestone_id));
@@ -159,7 +159,7 @@ export function registerLifecycleTool(api: OpenClawPluginApi, db: OpcDatabase):
159
159
 
160
160
  case "generate_report": {
161
161
  const company = db.queryOne("SELECT * FROM opc_companies WHERE id = ?", p.company_id);
162
- if (!company) return json({ error: "公司不存在" });
162
+ if (!company) return toolError("公司不存在", "COMPANY_NOT_FOUND");
163
163
 
164
164
  const employees = db.query(
165
165
  "SELECT COUNT(*) as count FROM opc_hr_records WHERE company_id = ? AND status = 'active'", p.company_id,
@@ -237,10 +237,10 @@ export function registerLifecycleTool(api: OpenClawPluginApi, db: OpcDatabase):
237
237
  }
238
238
 
239
239
  default:
240
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
240
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
241
241
  }
242
242
  } catch (err) {
243
- return json({ error: err instanceof Error ? err.message : String(err) });
243
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
244
244
  }
245
245
  },
246
246
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const MediaSchema = Type.Union([
11
11
  Type.Object({
@@ -130,7 +130,9 @@ export function registerMediaTool(api: OpenClawPluginApi, db: OpcDatabase): void
130
130
  fields.push("updated_at = ?"); values.push(new Date().toISOString());
131
131
  values.push(p.content_id);
132
132
  db.execute(`UPDATE opc_media_content SET ${fields.join(", ")} WHERE id = ?`, ...values);
133
- return json(db.queryOne("SELECT * FROM opc_media_content WHERE id = ?", p.content_id) ?? { error: "内容不存在" });
133
+ const updated = db.queryOne("SELECT * FROM opc_media_content WHERE id = ?", p.content_id);
134
+ if (!updated) return toolError("内容不存在", "RECORD_NOT_FOUND");
135
+ return json(updated);
134
136
  }
135
137
 
136
138
  case "content_calendar": {
@@ -149,7 +151,7 @@ export function registerMediaTool(api: OpenClawPluginApi, db: OpcDatabase): void
149
151
  case "platform_guide": {
150
152
  const guide = PLATFORM_GUIDES[p.platform];
151
153
  if (!guide) {
152
- return json({ error: `无此平台指南,可用: ${Object.keys(PLATFORM_GUIDES).join(", ")}` });
154
+ return toolError(`无此平台指南,可用: ${Object.keys(PLATFORM_GUIDES).join(", ")}`, "INVALID_INPUT");
153
155
  }
154
156
  return json({ platform: p.platform, ...guide });
155
157
  }
@@ -160,10 +162,10 @@ export function registerMediaTool(api: OpenClawPluginApi, db: OpcDatabase): void
160
162
  }
161
163
 
162
164
  default:
163
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
165
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
164
166
  }
165
167
  } catch (err) {
166
- return json({ error: err instanceof Error ? err.message : String(err) });
168
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
167
169
  }
168
170
  },
169
171
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const MonitoringSchema = Type.Union([
11
11
  Type.Object({
@@ -120,7 +120,9 @@ export function registerMonitoringTool(api: OpenClawPluginApi, db: OpcDatabase):
120
120
  "UPDATE opc_alerts SET status = 'resolved', resolved_at = ? WHERE id = ?",
121
121
  now, p.alert_id,
122
122
  );
123
- return json(db.queryOne("SELECT * FROM opc_alerts WHERE id = ?", p.alert_id) ?? { error: "告警不存在" });
123
+ const alert = db.queryOne("SELECT * FROM opc_alerts WHERE id = ?", p.alert_id);
124
+ if (!alert) return toolError("告警不存在", "RECORD_NOT_FOUND");
125
+ return json(alert);
124
126
  }
125
127
 
126
128
  case "kpi_summary": {
@@ -193,10 +195,10 @@ export function registerMonitoringTool(api: OpenClawPluginApi, db: OpcDatabase):
193
195
  }
194
196
 
195
197
  default:
196
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
198
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
197
199
  }
198
200
  } catch (err) {
199
- return json({ error: err instanceof Error ? err.message : String(err) });
201
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
200
202
  }
201
203
  },
202
204
  },
@@ -8,7 +8,7 @@
8
8
  import { Type, type Static } from "@sinclair/typebox";
9
9
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
10
10
  import type { OpcDatabase } from "../db/index.js";
11
- import { json } from "../utils/tool-helper.js";
11
+ import { json, toolError } from "../utils/tool-helper.js";
12
12
 
13
13
  const OPB_FIELDS = [
14
14
  "track", // 赛道(所处行业/细分市场)
@@ -110,7 +110,7 @@ export function registerOpbTool(api: OpenClawPluginApi, db: OpcDatabase): void {
110
110
  "SELECT id FROM opc_opb_canvas WHERE company_id = ?", p.company_id,
111
111
  ) as { id: string } | null;
112
112
  if (existing) {
113
- return json({ ok: false, error: "该公司画布已存在,请使用 canvas_update 更新" });
113
+ return toolError("该公司画布已存在,请使用 canvas_update 更新", "VALIDATION_ERROR");
114
114
  }
115
115
  const id = db.genId();
116
116
  const now = new Date().toISOString();
@@ -139,7 +139,7 @@ export function registerOpbTool(api: OpenClawPluginApi, db: OpcDatabase): void {
139
139
  "SELECT * FROM opc_opb_canvas WHERE company_id = ?", p.company_id,
140
140
  ) as CanvasRow | null;
141
141
  if (!canvas) {
142
- return json({ ok: false, error: "该公司暂无 OPB 画布,请先使用 canvas_init 创建" });
142
+ return toolError("该公司暂无 OPB 画布,请先使用 canvas_init 创建", "RECORD_NOT_FOUND");
143
143
  }
144
144
  // Calculate completion percentage
145
145
  const filled = OPB_FIELDS.filter(f => canvas[f as keyof CanvasRow] && String(canvas[f as keyof CanvasRow]).trim() !== "").length;
@@ -152,7 +152,7 @@ export function registerOpbTool(api: OpenClawPluginApi, db: OpcDatabase): void {
152
152
  "SELECT id FROM opc_opb_canvas WHERE company_id = ?", p.company_id,
153
153
  ) as { id: string } | null;
154
154
  if (!existing) {
155
- return json({ ok: false, error: "该公司暂无 OPB 画布,请先使用 canvas_init 创建" });
155
+ return toolError("该公司暂无 OPB 画布,请先使用 canvas_init 创建", "RECORD_NOT_FOUND");
156
156
  }
157
157
  const now = new Date().toISOString();
158
158
  const updates: string[] = [];
@@ -165,7 +165,7 @@ export function registerOpbTool(api: OpenClawPluginApi, db: OpcDatabase): void {
165
165
  }
166
166
  }
167
167
  if (updates.length === 0) {
168
- return json({ ok: false, error: "未提供任何更新字段" });
168
+ return toolError("未提供任何更新字段", "VALIDATION_ERROR");
169
169
  }
170
170
  updates.push("updated_at = ?");
171
171
  vals.push(now, existing.id);
@@ -180,10 +180,10 @@ export function registerOpbTool(api: OpenClawPluginApi, db: OpcDatabase): void {
180
180
  }
181
181
 
182
182
  default:
183
- return json({ error: "未知 action" });
183
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
184
184
  }
185
185
  } catch (err) {
186
- return json({ error: err instanceof Error ? err.message : String(err) });
186
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
187
187
  }
188
188
  },
189
189
  },
@@ -10,7 +10,7 @@ import { CompanyManager } from "../opc/company-manager.js";
10
10
  import type { OpcCompanyStatus, OpcTransactionCategory, OpcTransactionType } from "../opc/types.js";
11
11
  import { ensureCompanyWorkspace } from "../opc/workspace-factory.js";
12
12
  import { OpcManageSchema, type OpcManageParams } from "./schemas.js";
13
- import { json } from "../utils/tool-helper.js";
13
+ import { json, toolError, validationError } from "../utils/tool-helper.js";
14
14
 
15
15
  /**
16
16
  * 将 company_id(可能是 DB ID 或公司名称)解析为实际数据库 ID。
@@ -75,34 +75,42 @@ export function registerOpcTool(api: OpenClawPluginApi, db: OpcDatabase): void {
75
75
  return json(company);
76
76
  }
77
77
 
78
- case "get_company":
79
- return json(manager.getCompany(p.company_id) ?? { error: "公司不存在" });
78
+ case "get_company": {
79
+ const found = manager.getCompany(p.company_id);
80
+ if (!found) return toolError(`公司 ${p.company_id} 不存在`, "COMPANY_NOT_FOUND");
81
+ return json(found);
82
+ }
80
83
 
81
84
  case "list_companies":
82
85
  return json(
83
86
  manager.listCompanies(p.status as OpcCompanyStatus | undefined),
84
87
  );
85
88
 
86
- case "update_company":
87
- return json(
88
- manager.updateCompany(p.company_id, {
89
- name: p.name,
90
- industry: p.industry,
91
- description: p.description,
92
- owner_contact: p.owner_contact,
93
- }) ?? { error: "公司不存在" },
94
- );
89
+ case "update_company": {
90
+ const updated = manager.updateCompany(p.company_id, {
91
+ name: p.name,
92
+ industry: p.industry,
93
+ description: p.description,
94
+ owner_contact: p.owner_contact,
95
+ });
96
+ if (!updated) return toolError(`公司 ${p.company_id} 不存在`, "COMPANY_NOT_FOUND");
97
+ return json(updated);
98
+ }
95
99
 
96
- case "activate_company":
97
- return json(manager.activateCompany(p.company_id) ?? { error: "公司不存在" });
100
+ case "activate_company": {
101
+ const activated = manager.activateCompany(p.company_id);
102
+ if (!activated) return toolError(`公司 ${p.company_id} 不存在`, "COMPANY_NOT_FOUND");
103
+ return json(activated);
104
+ }
98
105
 
99
- case "change_company_status":
100
- return json(
101
- manager.transitionStatus(
102
- p.company_id,
103
- p.new_status as OpcCompanyStatus,
104
- ) ?? { error: "公司不存在" },
106
+ case "change_company_status": {
107
+ const transitioned = manager.transitionStatus(
108
+ p.company_id,
109
+ p.new_status as OpcCompanyStatus,
105
110
  );
111
+ if (!transitioned) return toolError(`公司 ${p.company_id} 不存在`, "COMPANY_NOT_FOUND");
112
+ return json(transitioned);
113
+ }
106
114
 
107
115
  // ── 交易记录 ──
108
116
  case "add_transaction":
@@ -160,7 +168,9 @@ export function registerOpcTool(api: OpenClawPluginApi, db: OpcDatabase): void {
160
168
  if (p.tags) updateData.tags = p.tags;
161
169
  if (p.notes) updateData.notes = p.notes;
162
170
  if (p.last_contact_date) updateData.last_contact_date = p.last_contact_date;
163
- return json(db.updateContact(p.contact_id, updateData) ?? { error: "联系人不存在" });
171
+ const updatedContact = db.updateContact(p.contact_id, updateData);
172
+ if (!updatedContact) return toolError(`联系人 ${p.contact_id} 不存在`, "CONTACT_NOT_FOUND");
173
+ return json(updatedContact);
164
174
  }
165
175
 
166
176
  case "delete_contact":
@@ -192,10 +202,10 @@ export function registerOpcTool(api: OpenClawPluginApi, db: OpcDatabase): void {
192
202
  }
193
203
 
194
204
  default:
195
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
205
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
196
206
  }
197
207
  } catch (err) {
198
- return json({ error: err instanceof Error ? err.message : String(err) });
208
+ return toolError(err instanceof Error ? err.message : String(err));
199
209
  }
200
210
  },
201
211
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const ProcurementSchema = Type.Union([
11
11
  Type.Object({
@@ -123,7 +123,7 @@ export function registerProcurementTool(api: OpenClawPluginApi, db: OpcDatabase)
123
123
  if (p.status !== undefined) { sets.push("status = ?"); vals.push(p.status); }
124
124
  if (p.delivery_date !== undefined) { sets.push("delivery_date = ?"); vals.push(p.delivery_date); }
125
125
  if (p.notes !== undefined) { sets.push("notes = ?"); vals.push(p.notes); }
126
- if (sets.length === 0) return json({ error: "未提供任何更新字段" });
126
+ if (sets.length === 0) return toolError("未提供任何更新字段", "VALIDATION_ERROR");
127
127
  vals.push(p.order_id);
128
128
  db.execute(`UPDATE opc_procurement_orders SET ${sets.join(", ")} WHERE id = ?`, ...vals);
129
129
  return json(db.queryOne("SELECT * FROM opc_procurement_orders WHERE id = ?", p.order_id));
@@ -177,10 +177,10 @@ export function registerProcurementTool(api: OpenClawPluginApi, db: OpcDatabase)
177
177
  }
178
178
 
179
179
  default:
180
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
180
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
181
181
  }
182
182
  } catch (err) {
183
- return json({ error: err instanceof Error ? err.message : String(err) });
183
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
184
184
  }
185
185
  },
186
186
  },
@@ -5,7 +5,7 @@
5
5
  import { Type, type Static } from "@sinclair/typebox";
6
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
7
  import type { OpcDatabase } from "../db/index.js";
8
- import { json } from "../utils/tool-helper.js";
8
+ import { json, toolError } from "../utils/tool-helper.js";
9
9
 
10
10
  const ProjectSchema = Type.Union([
11
11
  Type.Object({
@@ -120,7 +120,9 @@ export function registerProjectTool(api: OpenClawPluginApi, db: OpcDatabase): vo
120
120
  fields.push("updated_at = ?"); values.push(new Date().toISOString());
121
121
  values.push(p.project_id);
122
122
  db.execute(`UPDATE opc_projects SET ${fields.join(", ")} WHERE id = ?`, ...values);
123
- return json(db.queryOne("SELECT * FROM opc_projects WHERE id = ?", p.project_id) ?? { error: "项目不存在" });
123
+ const updated = db.queryOne("SELECT * FROM opc_projects WHERE id = ?", p.project_id);
124
+ if (!updated) return toolError("项目不存在", "RECORD_NOT_FOUND");
125
+ return json(updated);
124
126
  }
125
127
 
126
128
  case "add_task": {
@@ -155,12 +157,14 @@ export function registerProjectTool(api: OpenClawPluginApi, db: OpcDatabase): vo
155
157
  fields.push("updated_at = ?"); values.push(new Date().toISOString());
156
158
  values.push(p.task_id);
157
159
  db.execute(`UPDATE opc_tasks SET ${fields.join(", ")} WHERE id = ?`, ...values);
158
- return json(db.queryOne("SELECT * FROM opc_tasks WHERE id = ?", p.task_id) ?? { error: "任务不存在" });
160
+ const updatedTask = db.queryOne("SELECT * FROM opc_tasks WHERE id = ?", p.task_id);
161
+ if (!updatedTask) return toolError("任务不存在", "RECORD_NOT_FOUND");
162
+ return json(updatedTask);
159
163
  }
160
164
 
161
165
  case "project_summary": {
162
166
  const project = db.queryOne("SELECT * FROM opc_projects WHERE id = ?", p.project_id);
163
- if (!project) return json({ error: "项目不存在" });
167
+ if (!project) return toolError("项目不存在", "RECORD_NOT_FOUND");
164
168
  const tasks = db.query("SELECT status, COUNT(*) as count, SUM(hours_estimated) as est, SUM(hours_actual) as actual FROM opc_tasks WHERE project_id = ? GROUP BY status", p.project_id);
165
169
  const overdue = db.query(
166
170
  "SELECT * FROM opc_tasks WHERE project_id = ? AND status != 'done' AND due_date != '' AND due_date < date('now')",
@@ -189,10 +193,10 @@ export function registerProjectTool(api: OpenClawPluginApi, db: OpcDatabase): vo
189
193
  }
190
194
 
191
195
  default:
192
- return json({ error: `未知操作: ${(p as { action: string }).action}` });
196
+ return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
193
197
  }
194
198
  } catch (err) {
195
- return json({ error: err instanceof Error ? err.message : String(err) });
199
+ return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
196
200
  }
197
201
  },
198
202
  },