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.
- package/index.ts +11 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/api/companies.ts +12 -1
- package/src/api/dashboard.ts +13 -2
- package/src/api/middleware.ts +44 -0
- package/src/api/rate-limiter.ts +79 -0
- package/src/api/routes.ts +19 -2
- package/src/db/sqlite-adapter.test.ts +304 -0
- package/src/db/sqlite-adapter.ts +1 -1
- package/src/opc/company-manager.test.ts +232 -0
- package/src/tools/acquisition-tool.test.ts +139 -0
- package/src/tools/acquisition-tool.ts +3 -3
- package/src/tools/asset-package-tool.ts +4 -4
- package/src/tools/finance-tool.test.ts +106 -0
- package/src/tools/finance-tool.ts +6 -4
- package/src/tools/hr-tool.test.ts +153 -0
- package/src/tools/hr-tool.ts +6 -4
- package/src/tools/investment-tool.ts +4 -4
- package/src/tools/legal-tool.ts +12 -7
- package/src/tools/lifecycle-tool.ts +5 -5
- package/src/tools/media-tool.ts +7 -5
- package/src/tools/monitoring-tool.ts +6 -4
- package/src/tools/opb-tool.ts +7 -7
- package/src/tools/opc-tool.ts +33 -23
- package/src/tools/procurement-tool.ts +4 -4
- package/src/tools/project-tool.ts +10 -6
- package/src/tools/schemas.ts +47 -43
- package/src/tools/staff-tool.ts +8 -6
- package/src/utils/tool-helper.ts +28 -2
- package/src/web/config-ui.ts +69 -15
|
@@ -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
|
+
});
|
package/src/tools/hr-tool.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
202
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
201
203
|
}
|
|
202
204
|
} catch (err) {
|
|
203
|
-
return
|
|
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
|
|
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
|
|
190
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
191
191
|
}
|
|
192
192
|
} catch (err) {
|
|
193
|
-
return
|
|
193
|
+
return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
|
|
194
194
|
}
|
|
195
195
|
},
|
|
196
196
|
},
|
package/src/tools/legal-tool.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
185
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
181
186
|
}
|
|
182
187
|
} catch (err) {
|
|
183
|
-
return
|
|
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
|
|
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
|
|
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
|
|
240
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
241
241
|
}
|
|
242
242
|
} catch (err) {
|
|
243
|
-
return
|
|
243
|
+
return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
|
|
244
244
|
}
|
|
245
245
|
},
|
|
246
246
|
},
|
package/src/tools/media-tool.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
165
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
164
166
|
}
|
|
165
167
|
} catch (err) {
|
|
166
|
-
return
|
|
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
|
-
|
|
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
|
|
198
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
197
199
|
}
|
|
198
200
|
} catch (err) {
|
|
199
|
-
return
|
|
201
|
+
return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
|
|
200
202
|
}
|
|
201
203
|
},
|
|
202
204
|
},
|
package/src/tools/opb-tool.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
183
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
184
184
|
}
|
|
185
185
|
} catch (err) {
|
|
186
|
-
return
|
|
186
|
+
return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
|
|
187
187
|
}
|
|
188
188
|
},
|
|
189
189
|
},
|
package/src/tools/opc-tool.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
|
205
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
196
206
|
}
|
|
197
207
|
} catch (err) {
|
|
198
|
-
return
|
|
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
|
|
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
|
|
180
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
181
181
|
}
|
|
182
182
|
} catch (err) {
|
|
183
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
196
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
193
197
|
}
|
|
194
198
|
} catch (err) {
|
|
195
|
-
return
|
|
199
|
+
return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
|
|
196
200
|
}
|
|
197
201
|
},
|
|
198
202
|
},
|