galaxy-opc-plugin 0.2.1 → 0.2.3

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 (57) hide show
  1. package/README.md +207 -10
  2. package/index.ts +350 -8
  3. package/openclaw.plugin.json +1 -1
  4. package/package.json +17 -3
  5. package/src/__tests__/e2e/company-lifecycle.test.ts +399 -0
  6. package/src/__tests__/integration/business-workflows.test.ts +366 -0
  7. package/src/__tests__/test-utils.ts +316 -0
  8. package/src/api/companies.ts +4 -0
  9. package/src/api/dashboard.ts +368 -16
  10. package/src/api/routes.ts +2 -2
  11. package/src/commands/opc-command.ts +422 -0
  12. package/src/db/index.ts +3 -0
  13. package/src/db/migrations.test.ts +324 -0
  14. package/src/db/migrations.ts +277 -0
  15. package/src/db/schema.ts +312 -0
  16. package/src/db/sqlite-adapter.ts +44 -2
  17. package/src/opc/accounting-parser.ts +178 -0
  18. package/src/opc/autonomy-rules.ts +132 -0
  19. package/src/opc/briefing-builder.ts +1331 -0
  20. package/src/opc/business-workflows.test.ts +535 -0
  21. package/src/opc/business-workflows.ts +325 -0
  22. package/src/opc/context-injector.ts +366 -28
  23. package/src/opc/daily-brief.ts +529 -0
  24. package/src/opc/event-triggers.ts +472 -0
  25. package/src/opc/intelligence-engine.ts +702 -0
  26. package/src/opc/milestone-detector.ts +251 -0
  27. package/src/opc/onboarding-flow.ts +332 -0
  28. package/src/opc/proactive-service.ts +466 -0
  29. package/src/opc/reminder-service.ts +4 -43
  30. package/src/opc/session-task-tracker.ts +60 -0
  31. package/src/opc/stage-detector.ts +168 -0
  32. package/src/opc/task-executor.ts +332 -0
  33. package/src/opc/task-templates.ts +179 -0
  34. package/src/tools/document-tool.ts +1176 -0
  35. package/src/tools/finance-tool.test-payment.ts +326 -0
  36. package/src/tools/finance-tool.test.ts +238 -0
  37. package/src/tools/finance-tool.ts +1574 -14
  38. package/src/tools/hr-tool.ts +10 -1
  39. package/src/tools/legal-tool.test.ts +251 -0
  40. package/src/tools/legal-tool.ts +26 -4
  41. package/src/tools/lifecycle-tool.test.ts +231 -0
  42. package/src/tools/media-tool.ts +156 -1
  43. package/src/tools/monitoring-tool.ts +134 -1
  44. package/src/tools/onboarding-tool.ts +233 -0
  45. package/src/tools/opc-tool.test.ts +250 -0
  46. package/src/tools/opc-tool.ts +251 -28
  47. package/src/tools/order-tool.ts +481 -0
  48. package/src/tools/project-tool.test.ts +218 -0
  49. package/src/tools/schemas.ts +80 -0
  50. package/src/tools/search-tool.ts +227 -0
  51. package/src/tools/smart-accounting-tool.ts +144 -0
  52. package/src/tools/staff-tool.ts +395 -2
  53. package/src/web/DASHBOARD_INTEGRATION_GUIDE.md +478 -0
  54. package/src/web/config-ui-patches.ts +389 -0
  55. package/src/web/config-ui.ts +4162 -3555
  56. package/src/web/dashboard-ui.ts +582 -0
  57. package/src/web/landing-page.ts +56 -6
package/src/db/schema.ts CHANGED
@@ -13,6 +13,9 @@ export const OPC_TABLES = {
13
13
  status TEXT NOT NULL DEFAULT 'pending',
14
14
  registered_capital REAL NOT NULL DEFAULT 0,
15
15
  description TEXT NOT NULL DEFAULT '',
16
+ onboarding_data TEXT DEFAULT '{}',
17
+ onboarding_stage TEXT DEFAULT '',
18
+ onboarding_completed INTEGER DEFAULT 0,
16
19
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
17
20
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
18
21
  )
@@ -57,11 +60,28 @@ export const OPC_TABLES = {
57
60
  tags TEXT NOT NULL DEFAULT '[]',
58
61
  notes TEXT NOT NULL DEFAULT '',
59
62
  last_contact_date TEXT NOT NULL DEFAULT (date('now')),
63
+ pipeline_stage TEXT NOT NULL DEFAULT 'lead',
64
+ follow_up_date TEXT NOT NULL DEFAULT '',
65
+ deal_value REAL NOT NULL DEFAULT 0,
66
+ source TEXT NOT NULL DEFAULT '',
60
67
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
61
68
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
69
+ UNIQUE(company_id, name),
62
70
  FOREIGN KEY (company_id) REFERENCES opc_companies(id)
63
71
  )
64
72
  `,
73
+
74
+ contact_interactions: `
75
+ CREATE TABLE IF NOT EXISTS opc_contact_interactions (
76
+ id TEXT PRIMARY KEY,
77
+ contact_id TEXT NOT NULL,
78
+ company_id TEXT NOT NULL,
79
+ interaction_type TEXT NOT NULL DEFAULT 'note',
80
+ content TEXT NOT NULL DEFAULT '',
81
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
82
+ FOREIGN KEY (contact_id) REFERENCES opc_contacts(id)
83
+ )
84
+ `,
65
85
  // ── Phase 2 表 ────────────────────────────────────────────
66
86
 
67
87
  invoices: `
@@ -108,6 +128,7 @@ export const OPC_TABLES = {
108
128
  title TEXT NOT NULL,
109
129
  counterparty TEXT NOT NULL DEFAULT '',
110
130
  contract_type TEXT NOT NULL DEFAULT '',
131
+ direction TEXT NOT NULL DEFAULT 'sales',
111
132
  amount REAL NOT NULL DEFAULT 0,
112
133
  start_date TEXT NOT NULL DEFAULT '',
113
134
  end_date TEXT NOT NULL DEFAULT '',
@@ -154,6 +175,9 @@ export const OPC_TABLES = {
154
175
  published_date TEXT NOT NULL DEFAULT '',
155
176
  tags TEXT NOT NULL DEFAULT '[]',
156
177
  metrics TEXT NOT NULL DEFAULT '{}',
178
+ reviewer TEXT NOT NULL DEFAULT '',
179
+ review_notes TEXT NOT NULL DEFAULT '',
180
+ approved_at TEXT NOT NULL DEFAULT '',
157
181
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
158
182
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
159
183
  FOREIGN KEY (company_id) REFERENCES opc_companies(id)
@@ -437,6 +461,129 @@ export const OPC_TABLES = {
437
461
  )
438
462
  `,
439
463
 
464
+ // ── AI 员工任务追踪表 ───────────────────────────────────────────
465
+
466
+ staff_tasks: `
467
+ CREATE TABLE IF NOT EXISTS opc_staff_tasks (
468
+ id TEXT PRIMARY KEY,
469
+ company_id TEXT NOT NULL,
470
+ staff_role TEXT NOT NULL,
471
+ title TEXT NOT NULL,
472
+ description TEXT NOT NULL DEFAULT '',
473
+ status TEXT NOT NULL DEFAULT 'pending',
474
+ priority TEXT NOT NULL DEFAULT 'normal',
475
+ task_type TEXT NOT NULL DEFAULT 'manual',
476
+ schedule TEXT NOT NULL DEFAULT 'on_demand',
477
+ result_summary TEXT NOT NULL DEFAULT '',
478
+ result_data TEXT NOT NULL DEFAULT '{}',
479
+ session_key TEXT NOT NULL DEFAULT '',
480
+ assigned_at TEXT NOT NULL DEFAULT (datetime('now')),
481
+ started_at TEXT NOT NULL DEFAULT '',
482
+ completed_at TEXT NOT NULL DEFAULT '',
483
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
484
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
485
+ )
486
+ `,
487
+
488
+ // ── 主动智能系统表 ─────────────────────────────────────────────
489
+
490
+ insights: `
491
+ CREATE TABLE IF NOT EXISTS opc_insights (
492
+ id TEXT PRIMARY KEY,
493
+ company_id TEXT NOT NULL,
494
+ insight_type TEXT NOT NULL DEFAULT 'data_gap',
495
+ category TEXT NOT NULL DEFAULT 'ops',
496
+ priority INTEGER NOT NULL DEFAULT 50,
497
+ title TEXT NOT NULL,
498
+ message TEXT NOT NULL DEFAULT '',
499
+ action_hint TEXT NOT NULL DEFAULT '',
500
+ staff_role TEXT NOT NULL DEFAULT '',
501
+ status TEXT NOT NULL DEFAULT 'active',
502
+ expires_at TEXT NOT NULL DEFAULT '',
503
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
504
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
505
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
506
+ )
507
+ `,
508
+
509
+ celebrations: `
510
+ CREATE TABLE IF NOT EXISTS opc_celebrations (
511
+ id TEXT PRIMARY KEY,
512
+ company_id TEXT NOT NULL,
513
+ celebration_type TEXT NOT NULL,
514
+ title TEXT NOT NULL,
515
+ message TEXT NOT NULL DEFAULT '',
516
+ metric_value REAL NOT NULL DEFAULT 0,
517
+ shown INTEGER NOT NULL DEFAULT 0,
518
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
519
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
520
+ )
521
+ `,
522
+
523
+ company_stage: `
524
+ CREATE TABLE IF NOT EXISTS opc_company_stage (
525
+ company_id TEXT PRIMARY KEY,
526
+ stage TEXT NOT NULL DEFAULT 'idea',
527
+ stage_label TEXT NOT NULL DEFAULT '构想阶段',
528
+ confidence REAL NOT NULL DEFAULT 0,
529
+ factors_json TEXT NOT NULL DEFAULT '{}',
530
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
531
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
532
+ )
533
+ `,
534
+
535
+ briefings: `
536
+ CREATE TABLE IF NOT EXISTS opc_briefings (
537
+ id TEXT PRIMARY KEY,
538
+ company_id TEXT NOT NULL,
539
+ briefing_date TEXT NOT NULL DEFAULT (date('now')),
540
+ stage TEXT NOT NULL DEFAULT '',
541
+ health_score REAL NOT NULL DEFAULT 0,
542
+ summary_json TEXT NOT NULL DEFAULT '{}',
543
+ insights_json TEXT NOT NULL DEFAULT '[]',
544
+ next_steps_json TEXT NOT NULL DEFAULT '[]',
545
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
546
+ UNIQUE(company_id, briefing_date),
547
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
548
+ )
549
+ `,
550
+
551
+ // ── 文档生成表 ─────────────────────────────────────────────────
552
+
553
+ documents: `
554
+ CREATE TABLE IF NOT EXISTS opc_documents (
555
+ id TEXT PRIMARY KEY,
556
+ company_id TEXT NOT NULL,
557
+ doc_type TEXT NOT NULL DEFAULT 'contract',
558
+ title TEXT NOT NULL,
559
+ template_key TEXT NOT NULL DEFAULT '',
560
+ content TEXT NOT NULL DEFAULT '',
561
+ variables TEXT NOT NULL DEFAULT '{}',
562
+ version INTEGER NOT NULL DEFAULT 1,
563
+ status TEXT NOT NULL DEFAULT 'draft',
564
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
565
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
566
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
567
+ )
568
+ `,
569
+
570
+ // ── 发票明细行表 ───────────────────────────────────────────────
571
+
572
+ invoice_items: `
573
+ CREATE TABLE IF NOT EXISTS opc_invoice_items (
574
+ id TEXT PRIMARY KEY,
575
+ invoice_id TEXT NOT NULL,
576
+ description TEXT NOT NULL DEFAULT '',
577
+ quantity REAL NOT NULL DEFAULT 1,
578
+ unit_price REAL NOT NULL DEFAULT 0,
579
+ amount REAL NOT NULL DEFAULT 0,
580
+ tax_rate REAL NOT NULL DEFAULT 0,
581
+ tax_amount REAL NOT NULL DEFAULT 0,
582
+ sort_order INTEGER NOT NULL DEFAULT 0,
583
+ FOREIGN KEY (invoice_id) REFERENCES opc_invoices(id)
584
+ )
585
+ `,
586
+
440
587
  // ── OPB 画布表 ───────────────────────────────────────────────
441
588
 
442
589
  opb_canvas: `
@@ -464,6 +611,121 @@ export const OPC_TABLES = {
464
611
  FOREIGN KEY (company_id) REFERENCES opc_companies(id)
465
612
  )
466
613
  `,
614
+
615
+ // ── 财务期间表 ─────────────────────────────────────────────────
616
+
617
+ financial_periods: `
618
+ CREATE TABLE IF NOT EXISTS opc_financial_periods (
619
+ id TEXT PRIMARY KEY,
620
+ company_id TEXT NOT NULL,
621
+ period_type TEXT NOT NULL,
622
+ start_date TEXT NOT NULL,
623
+ end_date TEXT NOT NULL,
624
+ revenue REAL NOT NULL DEFAULT 0,
625
+ cost REAL NOT NULL DEFAULT 0,
626
+ profit REAL NOT NULL DEFAULT 0,
627
+ cash_flow REAL NOT NULL DEFAULT 0,
628
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
629
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
630
+ UNIQUE(company_id, period_type, start_date),
631
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
632
+ )
633
+ `,
634
+
635
+ // ── 付款记录表 ─────────────────────────────────────────────────
636
+
637
+ payments: `
638
+ CREATE TABLE IF NOT EXISTS opc_payments (
639
+ id TEXT PRIMARY KEY,
640
+ company_id TEXT NOT NULL,
641
+ direction TEXT NOT NULL DEFAULT 'receivable',
642
+ counterparty TEXT NOT NULL DEFAULT '',
643
+ amount REAL NOT NULL DEFAULT 0,
644
+ paid_amount REAL NOT NULL DEFAULT 0,
645
+ status TEXT NOT NULL DEFAULT 'pending',
646
+ due_date TEXT NOT NULL DEFAULT '',
647
+ paid_date TEXT NOT NULL DEFAULT '',
648
+ invoice_id TEXT NOT NULL DEFAULT '',
649
+ contract_id TEXT NOT NULL DEFAULT '',
650
+ category TEXT NOT NULL DEFAULT '',
651
+ payment_method TEXT NOT NULL DEFAULT '',
652
+ notes TEXT NOT NULL DEFAULT '',
653
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
654
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
655
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
656
+ )
657
+ `,
658
+
659
+ // ── 报价单表 ─────────────────────────────────────────────────
660
+
661
+ quotations: `
662
+ CREATE TABLE IF NOT EXISTS opc_quotations (
663
+ id TEXT PRIMARY KEY,
664
+ company_id TEXT NOT NULL,
665
+ contact_id TEXT NOT NULL DEFAULT '',
666
+ quotation_number TEXT NOT NULL,
667
+ title TEXT NOT NULL,
668
+ total_amount REAL NOT NULL DEFAULT 0,
669
+ valid_until TEXT NOT NULL DEFAULT '',
670
+ status TEXT NOT NULL DEFAULT 'draft',
671
+ notes TEXT NOT NULL DEFAULT '',
672
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
673
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
674
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id),
675
+ FOREIGN KEY (contact_id) REFERENCES opc_contacts(id)
676
+ )
677
+ `,
678
+
679
+ quotation_items: `
680
+ CREATE TABLE IF NOT EXISTS opc_quotation_items (
681
+ id TEXT PRIMARY KEY,
682
+ quotation_id TEXT NOT NULL,
683
+ description TEXT NOT NULL,
684
+ quantity REAL NOT NULL DEFAULT 1,
685
+ unit_price REAL NOT NULL DEFAULT 0,
686
+ total_price REAL NOT NULL DEFAULT 0,
687
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
688
+ FOREIGN KEY (quotation_id) REFERENCES opc_quotations(id)
689
+ )
690
+ `,
691
+
692
+ contract_milestones: `
693
+ CREATE TABLE IF NOT EXISTS opc_contract_milestones (
694
+ id TEXT PRIMARY KEY,
695
+ contract_id TEXT NOT NULL,
696
+ company_id TEXT NOT NULL,
697
+ title TEXT NOT NULL,
698
+ description TEXT NOT NULL DEFAULT '',
699
+ due_date TEXT NOT NULL DEFAULT '',
700
+ amount REAL NOT NULL DEFAULT 0,
701
+ status TEXT NOT NULL DEFAULT 'pending',
702
+ completed_date TEXT NOT NULL DEFAULT '',
703
+ notes TEXT NOT NULL DEFAULT '',
704
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
705
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
706
+ FOREIGN KEY (contract_id) REFERENCES opc_contracts(id),
707
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
708
+ )
709
+ `,
710
+
711
+ // ── 待办事项表 ─────────────────────────────────────────────────
712
+
713
+ todos: `
714
+ CREATE TABLE IF NOT EXISTS opc_todos (
715
+ id TEXT PRIMARY KEY,
716
+ company_id TEXT NOT NULL,
717
+ title TEXT NOT NULL,
718
+ description TEXT NOT NULL DEFAULT '',
719
+ priority TEXT NOT NULL DEFAULT 'normal',
720
+ status TEXT NOT NULL DEFAULT 'pending',
721
+ due_date TEXT NOT NULL DEFAULT '',
722
+ related_type TEXT NOT NULL DEFAULT '',
723
+ related_id TEXT NOT NULL DEFAULT '',
724
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
725
+ completed_at TEXT NOT NULL DEFAULT '',
726
+ FOREIGN KEY (company_id) REFERENCES opc_companies(id)
727
+ )
728
+ `,
467
729
  } as const;
468
730
 
469
731
  export const OPC_INDEXES = [
@@ -513,6 +775,56 @@ export const OPC_INDEXES = [
513
775
  "CREATE INDEX IF NOT EXISTS idx_financing_fees_transfer ON opc_financing_fees(transfer_id)",
514
776
  "CREATE INDEX IF NOT EXISTS idx_financing_fees_status ON opc_financing_fees(status)",
515
777
  "CREATE INDEX IF NOT EXISTS idx_staff_config_company ON opc_staff_config(company_id)",
778
+ // AI 员工任务
779
+ "CREATE INDEX IF NOT EXISTS idx_staff_tasks_company ON opc_staff_tasks(company_id)",
780
+ "CREATE INDEX IF NOT EXISTS idx_staff_tasks_role ON opc_staff_tasks(staff_role)",
781
+ "CREATE INDEX IF NOT EXISTS idx_staff_tasks_status ON opc_staff_tasks(status)",
782
+ // session_key 索引由 migration v9 创建(旧库此时列尚不存在)
783
+ // CRM(新表索引可在此创建,contacts 列索引由 migration v10 创建,旧库此时列尚不存在)
784
+ "CREATE INDEX IF NOT EXISTS idx_interactions_contact ON opc_contact_interactions(contact_id)",
785
+ // Documents
786
+ "CREATE INDEX IF NOT EXISTS idx_documents_company ON opc_documents(company_id)",
787
+ "CREATE INDEX IF NOT EXISTS idx_documents_type ON opc_documents(doc_type)",
788
+ // Invoice Items
789
+ "CREATE INDEX IF NOT EXISTS idx_invoice_items_invoice ON opc_invoice_items(invoice_id)",
516
790
  // OPB Canvas
517
791
  "CREATE INDEX IF NOT EXISTS idx_opb_canvas_company ON opc_opb_canvas(company_id)",
792
+ // 主动智能系统
793
+ "CREATE INDEX IF NOT EXISTS idx_insights_company ON opc_insights(company_id)",
794
+ "CREATE INDEX IF NOT EXISTS idx_insights_status ON opc_insights(status)",
795
+ "CREATE INDEX IF NOT EXISTS idx_insights_type ON opc_insights(insight_type)",
796
+ "CREATE INDEX IF NOT EXISTS idx_insights_expires ON opc_insights(expires_at)",
797
+ "CREATE INDEX IF NOT EXISTS idx_celebrations_company ON opc_celebrations(company_id)",
798
+ "CREATE INDEX IF NOT EXISTS idx_celebrations_shown ON opc_celebrations(shown)",
799
+ "CREATE INDEX IF NOT EXISTS idx_briefings_company_date ON opc_briefings(company_id, briefing_date)",
800
+ // 财务期间表索引
801
+ "CREATE INDEX IF NOT EXISTS idx_financial_periods_company ON opc_financial_periods(company_id)",
802
+ "CREATE INDEX IF NOT EXISTS idx_financial_periods_dates ON opc_financial_periods(start_date, end_date)",
803
+ // 付款记录表索引
804
+ "CREATE INDEX IF NOT EXISTS idx_payments_company ON opc_payments(company_id)",
805
+ "CREATE INDEX IF NOT EXISTS idx_payments_direction ON opc_payments(direction)",
806
+ "CREATE INDEX IF NOT EXISTS idx_payments_status ON opc_payments(status)",
807
+ "CREATE INDEX IF NOT EXISTS idx_payments_due_date ON opc_payments(due_date)",
808
+ "CREATE INDEX IF NOT EXISTS idx_payments_company_status ON opc_payments(company_id, status)",
809
+ "CREATE INDEX IF NOT EXISTS idx_payments_company_direction ON opc_payments(company_id, direction)",
810
+ // 报价单和里程碑索引
811
+ "CREATE INDEX IF NOT EXISTS idx_quotations_company ON opc_quotations(company_id)",
812
+ "CREATE INDEX IF NOT EXISTS idx_quotations_status ON opc_quotations(status)",
813
+ "CREATE INDEX IF NOT EXISTS idx_quotations_contact ON opc_quotations(contact_id)",
814
+ "CREATE INDEX IF NOT EXISTS idx_quotation_items_quotation ON opc_quotation_items(quotation_id)",
815
+ "CREATE INDEX IF NOT EXISTS idx_contract_milestones_contract ON opc_contract_milestones(contract_id)",
816
+ "CREATE INDEX IF NOT EXISTS idx_contract_milestones_status ON opc_contract_milestones(status)",
817
+ "CREATE INDEX IF NOT EXISTS idx_contract_milestones_due_date ON opc_contract_milestones(due_date)",
818
+ "CREATE INDEX IF NOT EXISTS idx_contracts_quotation ON opc_contracts(quotation_id)",
819
+ "CREATE INDEX IF NOT EXISTS idx_contracts_signed_date ON opc_contracts(signed_date)",
820
+ "CREATE INDEX IF NOT EXISTS idx_payments_milestone ON opc_payments(milestone_id)",
821
+ "CREATE INDEX IF NOT EXISTS idx_payments_risk_level ON opc_payments(risk_level)",
822
+ "CREATE INDEX IF NOT EXISTS idx_payments_overdue ON opc_payments(overdue_days)",
823
+ // 待办事项表索引
824
+ "CREATE INDEX IF NOT EXISTS idx_todos_company ON opc_todos(company_id)",
825
+ "CREATE INDEX IF NOT EXISTS idx_todos_status ON opc_todos(status)",
826
+ "CREATE INDEX IF NOT EXISTS idx_todos_priority ON opc_todos(priority)",
827
+ "CREATE INDEX IF NOT EXISTS idx_todos_due_date ON opc_todos(due_date)",
828
+ "CREATE INDEX IF NOT EXISTS idx_todos_company_status ON opc_todos(company_id, status)",
829
+ "CREATE INDEX IF NOT EXISTS idx_todos_company_priority ON opc_todos(company_id, priority)",
518
830
  ];
@@ -59,6 +59,11 @@ export class SqliteAdapter implements OpcDatabase {
59
59
  return generateId();
60
60
  }
61
61
 
62
+ /** 在事务中执行回调,失败自动回滚 */
63
+ transaction<T>(fn: () => T): T {
64
+ return this.db.transaction(fn)();
65
+ }
66
+
62
67
  close(): void {
63
68
  this.db.close();
64
69
  }
@@ -127,8 +132,45 @@ export class SqliteAdapter implements OpcDatabase {
127
132
  }
128
133
 
129
134
  deleteCompany(id: string): boolean {
130
- const result = this.db.prepare("DELETE FROM opc_companies WHERE id = ?").run(id);
131
- return result.changes > 0;
135
+ console.log(`[OPC DB] Deleting company: ${id}`);
136
+ // SQLite PRAGMA foreign_keys 必须在事务外设置
137
+ // 关闭外键约束后,在事务中完成所有删除,再恢复
138
+ this.db.pragma('foreign_keys = OFF');
139
+ try {
140
+ const deleted = this.transaction(() => {
141
+ // 孙表(引用其他 opc_ 表,不直接引用 company)
142
+ this.db.prepare("DELETE FROM opc_quotation_items WHERE quotation_id IN (SELECT id FROM opc_quotations WHERE company_id = ?)").run(id);
143
+ this.db.prepare("DELETE FROM opc_contract_milestones WHERE contract_id IN (SELECT id FROM opc_contracts WHERE company_id = ?)").run(id);
144
+ this.db.prepare("DELETE FROM opc_invoice_items WHERE invoice_id IN (SELECT id FROM opc_invoices WHERE company_id = ?)").run(id);
145
+ this.db.prepare("DELETE FROM opc_contact_interactions WHERE contact_id IN (SELECT id FROM opc_contacts WHERE company_id = ?)").run(id);
146
+
147
+ // 所有直接引用 company_id 的表
148
+ const tables = [
149
+ 'opc_quotations', 'opc_contracts', 'opc_payments', 'opc_transactions',
150
+ 'opc_invoices', 'opc_contacts', 'opc_tax_filings', 'opc_employees',
151
+ 'opc_projects', 'opc_tasks', 'opc_media_content', 'opc_investment_rounds',
152
+ 'opc_investors', 'opc_services', 'opc_procurement_orders',
153
+ 'opc_lifecycle_events', 'opc_milestones', 'opc_alerts', 'opc_metrics',
154
+ 'opc_staff_tasks', 'opc_staff_config', 'opc_acquisition_cases',
155
+ 'opc_asset_package_items', 'opc_opb_canvas', 'opc_insights',
156
+ 'opc_celebrations', 'opc_company_stage', 'opc_briefings', 'opc_documents',
157
+ 'opc_biz_changes', 'opc_financial_periods', 'opc_todos', 'opc_hr_records',
158
+ 'opc_contact_interactions'
159
+ ];
160
+ for (const table of tables) {
161
+ try {
162
+ this.db.prepare(`DELETE FROM ${table} WHERE company_id = ?`).run(id);
163
+ } catch (_) { /* 表可能不存在,忽略 */ }
164
+ }
165
+
166
+ const result = this.db.prepare("DELETE FROM opc_companies WHERE id = ?").run(id);
167
+ console.log(`[OPC DB] Deleted: ${result.changes}`);
168
+ return result.changes > 0;
169
+ });
170
+ return deleted;
171
+ } finally {
172
+ this.db.pragma('foreign_keys = ON');
173
+ }
132
174
  }
133
175
 
134
176
  // ── Employees ──────────────────────────────────────────────
@@ -0,0 +1,178 @@
1
+ /**
2
+ * 星环OPC中心 — 智能记账意图解析器
3
+ * 使用 LLM 解析自然语言记账输入
4
+ */
5
+
6
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
+
8
+ export interface AccountingIntent {
9
+ type: "income" | "expense";
10
+ amount: number;
11
+ description: string;
12
+ category: string;
13
+ date: string;
14
+ counterparty?: string;
15
+ is_deductible?: boolean;
16
+ tax_note?: string;
17
+ confidence: number;
18
+ }
19
+
20
+ /**
21
+ * 解析自然语言记账意图
22
+ * @param input 用户输入的自然语言
23
+ * @param api OpenClaw API
24
+ * @returns 解析后的记账意图
25
+ */
26
+ export async function parseAccountingIntent(
27
+ input: string,
28
+ api: OpenClawPluginApi,
29
+ ): Promise<AccountingIntent> {
30
+ const systemPrompt = `你是一个专业的会计助手,负责解析用户的自然语言记账输入。
31
+
32
+ 你的任务是从用户输入中提取以下信息:
33
+ 1. 交易类型(income 或 expense)
34
+ 2. 金额(数字,单位:元)
35
+ 3. 描述(简短描述)
36
+ 4. 分类(从以下类别中选择):
37
+ - 收入类:service_income(服务收入), product_income(产品销售), investment_income(投资收益), other_income(其他收入)
38
+ - 支出类:business_entertainment(业务招待费), office_supplies(办公费用), marketing(营销费用), salary(工资), rent(租金), utilities(水电费), fixed_asset(固定资产), tax(税费), other_expense(其他支出)
39
+ 5. 日期(YYYY-MM-DD 格式,如果未指定则使用今天)
40
+ 6. 交易对方(如果提到)
41
+ 7. 是否可抵税(布尔值)
42
+ 8. 税务提示(如果可抵税或有特殊税务处理,给出提示)
43
+
44
+ 分类规则:
45
+ - 业务招待费:请客吃饭、送礼、招待客户等 → 可抵扣,需保存发票
46
+ - 办公费用:购买办公用品、设备维护等 → 可抵扣
47
+ - 固定资产:购买电脑、家具等大额设备(>2000元) → 可折旧
48
+ - 营销费用:广告投放、推广活动等 → 可抵扣
49
+ - 工资:员工工资 → 需代扣个税
50
+ - 租金:办公场地租金 → 可抵扣
51
+ - 税费:各类税费缴纳 → 不可抵扣
52
+ - 生活支出:个人消费 → 不可抵扣
53
+
54
+ 请以 JSON 格式返回解析结果,格式如下:
55
+ {
56
+ "type": "income" 或 "expense",
57
+ "amount": 金额(数字),
58
+ "description": "描述",
59
+ "category": "分类",
60
+ "date": "YYYY-MM-DD",
61
+ "counterparty": "交易对方(可选)",
62
+ "is_deductible": true/false,
63
+ "tax_note": "税务提示(可选)",
64
+ "confidence": 0-100(解析置信度)
65
+ }
66
+
67
+ 如果无法准确解析,confidence 设为较低值,并在 description 中说明原因。`;
68
+
69
+ const userPrompt = `请解析以下记账输入:\n\n${input}`;
70
+
71
+ try {
72
+ const response = await api.callLLM({
73
+ messages: [
74
+ { role: "system", content: systemPrompt },
75
+ { role: "user", content: userPrompt },
76
+ ],
77
+ maxTokens: 500,
78
+ temperature: 0.1, // 低温度确保稳定输出
79
+ });
80
+
81
+ const content = response.content[0];
82
+ if (content.type !== "text") {
83
+ throw new Error("LLM 返回非文本内容");
84
+ }
85
+
86
+ // 尝试提取 JSON
87
+ const text = content.text;
88
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
89
+ if (!jsonMatch) {
90
+ throw new Error("无法从 LLM 响应中提取 JSON");
91
+ }
92
+
93
+ const result = JSON.parse(jsonMatch[0]) as AccountingIntent;
94
+
95
+ // 验证必填字段
96
+ if (!result.type || !result.amount || !result.description || !result.category) {
97
+ throw new Error("解析结果缺少必填字段");
98
+ }
99
+
100
+ // 如果没有日期,使用今天
101
+ if (!result.date) {
102
+ result.date = new Date().toISOString().slice(0, 10);
103
+ }
104
+
105
+ return result;
106
+ } catch (error) {
107
+ api.logger.error("opc: 智能记账解析失败", error);
108
+
109
+ // 返回一个低置信度的默认结果
110
+ return {
111
+ type: "expense",
112
+ amount: 0,
113
+ description: `解析失败:${error instanceof Error ? error.message : String(error)}`,
114
+ category: "other_expense",
115
+ date: new Date().toISOString().slice(0, 10),
116
+ is_deductible: false,
117
+ confidence: 0,
118
+ };
119
+ }
120
+ }
121
+
122
+ /**
123
+ * 基于历史记录智能建议分类
124
+ * @param description 交易描述
125
+ * @param api OpenClaw API
126
+ * @returns 建议的分类
127
+ */
128
+ export async function suggestCategory(
129
+ description: string,
130
+ api: OpenClawPluginApi,
131
+ ): Promise<{ category: string; reason: string; confidence: number }> {
132
+ const systemPrompt = `你是一个专业的会计分类助手。根据交易描述,建议最合适的分类。
133
+
134
+ 分类选项:
135
+ - 收入类:service_income(服务收入), product_income(产品销售), investment_income(投资收益), other_income(其他收入)
136
+ - 支出类:business_entertainment(业务招待费), office_supplies(办公费用), marketing(营销费用), salary(工资), rent(租金), utilities(水电费), fixed_asset(固定资产), tax(税费), other_expense(其他支出)
137
+
138
+ 请以 JSON 格式返回:
139
+ {
140
+ "category": "分类代码",
141
+ "reason": "选择理由",
142
+ "confidence": 0-100
143
+ }`;
144
+
145
+ const userPrompt = `请为以下交易描述建议分类:\n\n${description}`;
146
+
147
+ try {
148
+ const response = await api.callLLM({
149
+ messages: [
150
+ { role: "system", content: systemPrompt },
151
+ { role: "user", content: userPrompt },
152
+ ],
153
+ maxTokens: 200,
154
+ temperature: 0.1,
155
+ });
156
+
157
+ const content = response.content[0];
158
+ if (content.type !== "text") {
159
+ throw new Error("LLM 返回非文本内容");
160
+ }
161
+
162
+ const text = content.text;
163
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
164
+ if (!jsonMatch) {
165
+ throw new Error("无法从 LLM 响应中提取 JSON");
166
+ }
167
+
168
+ const result = JSON.parse(jsonMatch[0]) as { category: string; reason: string; confidence: number };
169
+ return result;
170
+ } catch (error) {
171
+ api.logger.error("opc: 智能分类建议失败", error);
172
+ return {
173
+ category: "other_expense",
174
+ reason: `分类失败:${error instanceof Error ? error.message : String(error)}`,
175
+ confidence: 0,
176
+ };
177
+ }
178
+ }