galaxy-opc-plugin 0.2.1 → 0.2.2

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 (41) hide show
  1. package/index.ts +244 -8
  2. package/package.json +17 -3
  3. package/src/__tests__/e2e/company-lifecycle.test.ts +399 -0
  4. package/src/__tests__/integration/business-workflows.test.ts +366 -0
  5. package/src/__tests__/test-utils.ts +316 -0
  6. package/src/commands/opc-command.ts +422 -0
  7. package/src/db/index.ts +3 -0
  8. package/src/db/migrations.test.ts +324 -0
  9. package/src/db/migrations.ts +131 -0
  10. package/src/db/schema.ts +211 -0
  11. package/src/db/sqlite-adapter.ts +5 -0
  12. package/src/opc/autonomy-rules.ts +132 -0
  13. package/src/opc/briefing-builder.ts +1331 -0
  14. package/src/opc/business-workflows.test.ts +535 -0
  15. package/src/opc/business-workflows.ts +325 -0
  16. package/src/opc/context-injector.ts +366 -28
  17. package/src/opc/event-triggers.ts +472 -0
  18. package/src/opc/intelligence-engine.ts +702 -0
  19. package/src/opc/milestone-detector.ts +251 -0
  20. package/src/opc/proactive-service.ts +179 -0
  21. package/src/opc/reminder-service.ts +4 -43
  22. package/src/opc/session-task-tracker.ts +60 -0
  23. package/src/opc/stage-detector.ts +168 -0
  24. package/src/opc/task-executor.ts +332 -0
  25. package/src/opc/task-templates.ts +179 -0
  26. package/src/tools/document-tool.ts +1176 -0
  27. package/src/tools/finance-tool.test.ts +238 -0
  28. package/src/tools/finance-tool.ts +922 -14
  29. package/src/tools/hr-tool.ts +10 -1
  30. package/src/tools/legal-tool.test.ts +251 -0
  31. package/src/tools/legal-tool.ts +26 -4
  32. package/src/tools/lifecycle-tool.test.ts +231 -0
  33. package/src/tools/media-tool.ts +156 -1
  34. package/src/tools/monitoring-tool.ts +134 -1
  35. package/src/tools/opc-tool.test.ts +250 -0
  36. package/src/tools/opc-tool.ts +251 -28
  37. package/src/tools/project-tool.test.ts +218 -0
  38. package/src/tools/schemas.ts +80 -0
  39. package/src/tools/search-tool.ts +227 -0
  40. package/src/tools/staff-tool.ts +395 -2
  41. package/src/web/config-ui.ts +299 -45
@@ -0,0 +1,399 @@
1
+ /**
2
+ * 星环OPC中心 — 端到端测试:完整公司生命周期
3
+ *
4
+ * 测试从公司注册到盈利的完整业务流程
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
8
+ import { createTestDb, factories } from "../test-utils.js";
9
+ import { SqliteAdapter } from "../../db/sqlite-adapter.js";
10
+ import { BusinessWorkflows } from "../../opc/business-workflows.js";
11
+
12
+ describe("company lifecycle E2E", () => {
13
+ let db: SqliteAdapter;
14
+ let workflows: BusinessWorkflows;
15
+
16
+ beforeEach(() => {
17
+ db = createTestDb();
18
+ workflows = new BusinessWorkflows(db);
19
+ });
20
+
21
+ afterEach(() => {
22
+ db.close();
23
+ });
24
+
25
+ it("complete journey: from registration to profitability", () => {
26
+ // ═══════════════════════════════════════════════════════════
27
+ // 第一步:注册公司
28
+ // ═══════════════════════════════════════════════════════════
29
+ const company = db.createCompany({
30
+ name: "张三的咨询公司",
31
+ industry: "咨询",
32
+ owner_name: "张三",
33
+ owner_contact: "13800138000",
34
+ status: "active",
35
+ registered_capital: 100000,
36
+ description: "专业提供商业咨询服务",
37
+ });
38
+
39
+ expect(company).not.toBeNull();
40
+ expect(company.id).toBeDefined();
41
+ expect(company.name).toBe("张三的咨询公司");
42
+
43
+ // ═══════════════════════════════════════════════════════════
44
+ // 第二步:添加创始人为员工
45
+ // ═══════════════════════════════════════════════════════════
46
+ const employeeId = db.genId();
47
+ db.execute(
48
+ `INSERT INTO opc_employees (id, company_id, name, role, skills, status, created_at)
49
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
50
+ employeeId, company.id, "张三", "general", "商业咨询,战略规划", "active"
51
+ );
52
+
53
+ const employee = db.queryOne(
54
+ "SELECT * FROM opc_employees WHERE id = ?",
55
+ employeeId
56
+ ) as any;
57
+ expect(employee.name).toBe("张三");
58
+
59
+ // ═══════════════════════════════════════════════════════════
60
+ // 第三步:签订首个服务合同
61
+ // ═══════════════════════════════════════════════════════════
62
+ const contractId = db.genId();
63
+ const now = new Date().toISOString();
64
+
65
+ db.execute(
66
+ `INSERT INTO opc_contracts
67
+ (id, company_id, title, counterparty, contract_type, amount, start_date, end_date, status, key_terms, risk_notes, reminder_date, created_at, updated_at)
68
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
69
+ contractId, company.id, "商业咨询服务合同", "创业公司A", "service", 100000,
70
+ "2026-01-01", "2026-06-30", "active",
71
+ "按月交付报告,分3期付款",
72
+ "",
73
+ "2026-06-15",
74
+ now, now
75
+ );
76
+
77
+ // 触发业务工作流(自动创建联系人等)
78
+ const contractResults = workflows.afterContractCreated({
79
+ id: contractId,
80
+ company_id: company.id,
81
+ title: "商业咨询服务合同",
82
+ counterparty: "创业公司A",
83
+ contract_type: "service",
84
+ direction: "sales",
85
+ amount: 100000,
86
+ start_date: "2026-01-01",
87
+ end_date: "2026-06-30",
88
+ });
89
+
90
+ expect(contractResults.length).toBeGreaterThan(0);
91
+
92
+ // 验证自动创建了客户联系人
93
+ const contact = db.queryOne(
94
+ "SELECT * FROM opc_contacts WHERE company_id = ? AND name = ?",
95
+ company.id, "创业公司A"
96
+ ) as any;
97
+ expect(contact).not.toBeNull();
98
+ expect(contact.tags).toContain("客户");
99
+
100
+ // ═══════════════════════════════════════════════════════════
101
+ // 第四步:记录收款(3期付款)
102
+ // ═══════════════════════════════════════════════════════════
103
+ const payments = [
104
+ { date: "2026-02-15", amount: 30000, desc: "第一期款" },
105
+ { date: "2026-04-15", amount: 30000, desc: "第二期款" },
106
+ { date: "2026-06-15", amount: 40000, desc: "第三期款(尾款)" },
107
+ ];
108
+
109
+ payments.forEach((payment) => {
110
+ const txId = db.genId();
111
+ db.execute(
112
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
113
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
114
+ txId, company.id, "income", "service_income", payment.amount,
115
+ payment.desc, "创业公司A", payment.date
116
+ );
117
+
118
+ // 触发交易工作流
119
+ workflows.afterTransactionCreated({
120
+ id: txId,
121
+ company_id: company.id,
122
+ type: "income",
123
+ amount: payment.amount,
124
+ counterparty: "创业公司A",
125
+ description: payment.desc,
126
+ });
127
+ });
128
+
129
+ // 验证总收入
130
+ const incomeSummary = db.queryOne(
131
+ `SELECT SUM(amount) as total_income
132
+ FROM opc_transactions
133
+ WHERE company_id = ? AND type = 'income'`,
134
+ company.id
135
+ ) as any;
136
+ expect(incomeSummary.total_income).toBe(100000);
137
+
138
+ // ═══════════════════════════════════════════════════════════
139
+ // 第五步:记录运营成本
140
+ // ═══════════════════════════════════════════════════════════
141
+ const expenses = [
142
+ { date: "2026-01-25", amount: 5000, category: "rent", desc: "办公室租金" },
143
+ { date: "2026-02-10", amount: 2000, category: "utilities", desc: "水电网费" },
144
+ { date: "2026-03-15", amount: 3000, category: "marketing", desc: "市场推广" },
145
+ { date: "2026-04-20", amount: 1500, category: "supplies", desc: "办公用品" },
146
+ ];
147
+
148
+ expenses.forEach((expense) => {
149
+ const txId = db.genId();
150
+ db.execute(
151
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
152
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
153
+ txId, company.id, "expense", expense.category, expense.amount,
154
+ expense.desc, "供应商", expense.date
155
+ );
156
+ });
157
+
158
+ // 验证总支出
159
+ const expenseSummary = db.queryOne(
160
+ `SELECT SUM(amount) as total_expense
161
+ FROM opc_transactions
162
+ WHERE company_id = ? AND type = 'expense'`,
163
+ company.id
164
+ ) as any;
165
+ expect(expenseSummary.total_expense).toBe(11500);
166
+
167
+ // ═══════════════════════════════════════════════════════════
168
+ // 第六步:验证财务状态(盈利)
169
+ // ═══════════════════════════════════════════════════════════
170
+ const financialSummary = db.queryOne(
171
+ `SELECT
172
+ COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_revenue,
173
+ COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_cost
174
+ FROM opc_transactions WHERE company_id = ?`,
175
+ company.id
176
+ ) as any;
177
+
178
+ const profit = financialSummary.total_revenue - financialSummary.total_cost;
179
+
180
+ expect(financialSummary.total_revenue).toBe(100000);
181
+ expect(financialSummary.total_cost).toBe(11500);
182
+ expect(profit).toBe(88500);
183
+ expect(profit).toBeGreaterThan(0); // 实现盈利
184
+
185
+ // ═══════════════════════════════════════════════════════════
186
+ // 第七步:验证里程碑记录
187
+ // ═══════════════════════════════════════════════════════════
188
+ const milestones = db.query(
189
+ "SELECT * FROM opc_milestones WHERE company_id = ?",
190
+ company.id
191
+ ) as any[];
192
+
193
+ // 检查里程碑是否存在 (业务工作流可能会创建)
194
+ // Schema uses: title, category instead of milestone_type
195
+ expect(milestones.length).toBeGreaterThanOrEqual(0);
196
+
197
+ // ═══════════════════════════════════════════════════════════
198
+ // 第八步:验证公司健康度指标
199
+ // ═══════════════════════════════════════════════════════════
200
+ const healthMetrics = {
201
+ // 1. 客户数量(检查是否有联系人被创建)
202
+ customerCount: (db.query(
203
+ "SELECT COUNT(*) as count FROM opc_contacts WHERE company_id = ?",
204
+ company.id
205
+ ) as any[])[0].count,
206
+
207
+ // 2. 活跃合同数
208
+ activeContracts: (db.query(
209
+ "SELECT COUNT(*) as count FROM opc_contracts WHERE company_id = ? AND status = 'active'",
210
+ company.id
211
+ ) as any[])[0].count,
212
+
213
+ // 3. 收入流水笔数
214
+ transactionCount: (db.query(
215
+ "SELECT COUNT(*) as count FROM opc_transactions WHERE company_id = ? AND type = 'income'",
216
+ company.id
217
+ ) as any[])[0].count,
218
+
219
+ // 4. 利润率
220
+ profitMargin: (profit / financialSummary.total_revenue) * 100,
221
+ };
222
+
223
+ expect(healthMetrics.customerCount).toBeGreaterThan(0);
224
+ expect(healthMetrics.activeContracts).toBeGreaterThan(0);
225
+ expect(healthMetrics.transactionCount).toBeGreaterThanOrEqual(3);
226
+ expect(healthMetrics.profitMargin).toBeGreaterThan(80); // 88.5%
227
+
228
+ // ═══════════════════════════════════════════════════════════
229
+ // 第九步:公司状态检查
230
+ // ═══════════════════════════════════════════════════════════
231
+ const finalCompany = db.getCompany(company.id);
232
+ expect(finalCompany).not.toBeNull();
233
+ expect(finalCompany!.status).toBe("active");
234
+ expect(finalCompany!.name).toBe("张三的咨询公司");
235
+
236
+ // ═══════════════════════════════════════════════════════════
237
+ // 测试总结:成功模拟了一个一人公司从注册到盈利的完整生命周期
238
+ // ═══════════════════════════════════════════════════════════
239
+ const summary = {
240
+ company: finalCompany!.name,
241
+ status: finalCompany!.status,
242
+ revenue: financialSummary.total_revenue,
243
+ cost: financialSummary.total_cost,
244
+ profit,
245
+ profitMargin: `${healthMetrics.profitMargin.toFixed(2)}%`,
246
+ customers: healthMetrics.customerCount,
247
+ contracts: healthMetrics.activeContracts,
248
+ milestones: milestones.length,
249
+ };
250
+
251
+ // 验证公司已经成功运营
252
+ expect(summary.profit).toBeGreaterThan(0);
253
+ expect(summary.customers).toBeGreaterThan(0);
254
+ expect(summary.contracts).toBeGreaterThan(0);
255
+
256
+ console.log("✓ E2E Test Summary:", summary);
257
+ });
258
+
259
+ it("multi-contract scenario: expanding business", () => {
260
+ // ═══════════════════════════════════════════════════════════
261
+ // 场景:公司接连签订多个合同,业务扩张
262
+ // ═══════════════════════════════════════════════════════════
263
+ const company = db.createCompany(factories.company({
264
+ name: "快速成长科技公司",
265
+ industry: "科技",
266
+ }));
267
+
268
+ // 签订3个不同客户的合同
269
+ const clients = ["客户A", "客户B", "客户C"];
270
+ const now = new Date().toISOString();
271
+
272
+ clients.forEach((client, index) => {
273
+ const contractId = db.genId();
274
+ const amount = (index + 1) * 50000;
275
+
276
+ db.execute(
277
+ `INSERT INTO opc_contracts
278
+ (id, company_id, title, counterparty, contract_type, amount, start_date, end_date, status, key_terms, risk_notes, reminder_date, created_at, updated_at)
279
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
280
+ contractId, company.id, `${client}服务合同`, client, "service", amount,
281
+ "2026-01-01", "2026-12-31", "active", "", "", "2026-11-30", now, now
282
+ );
283
+
284
+ workflows.afterContractCreated({
285
+ id: contractId,
286
+ company_id: company.id,
287
+ title: `${client}服务合同`,
288
+ counterparty: client,
289
+ contract_type: "service",
290
+ direction: "sales",
291
+ amount,
292
+ start_date: "2026-01-01",
293
+ end_date: "2026-12-31",
294
+ });
295
+
296
+ // 记录收款
297
+ const txId = db.genId();
298
+ db.execute(
299
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
300
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
301
+ txId, company.id, "income", "service_income", amount,
302
+ `${client}项目收款`, client, "2026-02-15"
303
+ );
304
+ });
305
+
306
+ // 验证:应该有3个客户
307
+ const customers = db.query(
308
+ "SELECT * FROM opc_contacts WHERE company_id = ? AND tags LIKE '%客户%'",
309
+ company.id
310
+ ) as any[];
311
+ expect(customers.length).toBe(3);
312
+
313
+ // 验证:应该有3个活跃合同
314
+ const contracts = db.query(
315
+ "SELECT * FROM opc_contracts WHERE company_id = ? AND status = 'active'",
316
+ company.id
317
+ ) as any[];
318
+ expect(contracts.length).toBe(3);
319
+
320
+ // 验证:总收入应该是 50000 + 100000 + 150000 = 300000
321
+ const totalRevenue = db.queryOne(
322
+ `SELECT SUM(amount) as total
323
+ FROM opc_transactions
324
+ WHERE company_id = ? AND type = 'income'`,
325
+ company.id
326
+ ) as any;
327
+ expect(totalRevenue.total).toBe(300000);
328
+ });
329
+
330
+ it("failure scenario: company with losses", () => {
331
+ // ═══════════════════════════════════════════════════════════
332
+ // 场景:公司亏损,支出大于收入
333
+ // ═══════════════════════════════════════════════════════════
334
+ const company = db.createCompany(factories.company({
335
+ name: "亏损公司案例",
336
+ industry: "零售",
337
+ }));
338
+
339
+ // 收入较少
340
+ const incomeId = db.genId();
341
+ db.execute(
342
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
343
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
344
+ incomeId, company.id, "income", "product_income", 30000,
345
+ "销售收入", "客户", "2026-01-15"
346
+ );
347
+
348
+ // 支出较多
349
+ const expenses = [
350
+ { amount: 20000, category: "rent", desc: "租金" },
351
+ { amount: 15000, category: "salary", desc: "工资" },
352
+ { amount: 10000, category: "marketing", desc: "推广" },
353
+ ];
354
+
355
+ expenses.forEach((expense) => {
356
+ const txId = db.genId();
357
+ db.execute(
358
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
359
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
360
+ txId, company.id, "expense", expense.category, expense.amount,
361
+ expense.desc, "供应商", "2026-01-20"
362
+ );
363
+ });
364
+
365
+ // 计算盈亏
366
+ const summary = db.queryOne(
367
+ `SELECT
368
+ COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as revenue,
369
+ COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as cost
370
+ FROM opc_transactions WHERE company_id = ?`,
371
+ company.id
372
+ ) as any;
373
+
374
+ const profit = summary.revenue - summary.cost;
375
+
376
+ expect(summary.revenue).toBe(30000);
377
+ expect(summary.cost).toBe(45000);
378
+ expect(profit).toBe(-15000);
379
+ expect(profit).toBeLessThan(0); // 确认亏损
380
+
381
+ // 这种情况下,公司可能需要进入收购流程
382
+ // 可以创建收购案例记录
383
+ const acquisitionId = db.genId();
384
+ const now = new Date().toISOString();
385
+ db.execute(
386
+ `INSERT INTO opc_acquisition_cases
387
+ (id, company_id, acquirer_id, case_type, status, trigger_reason, acquisition_price, loss_amount, tax_deduction, created_at, updated_at)
388
+ VALUES (?, ?, 'starriver', 'acquisition', 'evaluating', '连续亏损', ?, ?, ?, ?, ?)`,
389
+ acquisitionId, company.id, 10000, 15000, 3750, now, now
390
+ );
391
+
392
+ const acquisitionCase = db.queryOne(
393
+ "SELECT * FROM opc_acquisition_cases WHERE id = ?",
394
+ acquisitionId
395
+ ) as any;
396
+ expect(acquisitionCase).not.toBeNull();
397
+ expect(acquisitionCase.loss_amount).toBe(15000);
398
+ });
399
+ });