diff --git a/docs/superpowers/plans/2026-04-20-freelance-itops-plugin-enhancement-plan.md b/docs/superpowers/plans/2026-04-20-freelance-itops-plugin-enhancement-plan.md new file mode 100644 index 0000000..85449fd --- /dev/null +++ b/docs/superpowers/plans/2026-04-20-freelance-itops-plugin-enhancement-plan.md @@ -0,0 +1,587 @@ +# freelance + itops 插件增强实施计划 + +> 日期: 2026-04-20 +> 对应规格: `docs/superpowers/specs/2026-04-20-freelance-itops-plugin-enhancement-design.md` +> 前置: 两插件已部署(freelance 10 实体/20 权限,itops 4 实体/8 权限) + +--- + +## 总览 + +| Phase | 内容 | 类型 | 依赖 | +|-------|------|------|------| +| P1 | freelance Layer 1 — 智能业务引擎 | 纯 plugin.toml | 无 | +| P2 | itops Layer 1 — 智能业务引擎 | 纯 plugin.toml | 无 | +| P3 | freelance Layer 3 — PDF 模板 | 纯 plugin.toml | 无 | +| P4 | itops Layer 3 — PDF 模板 | 纯 plugin.toml | 无 | +| P5 | 平台 dashboard widgets 扩展 | manifest.rs + 前端 | P1-P4 完成 | +| P6 | freelance + itops Layer 2 — 仪表盘 | plugin.toml + 前端 | P5 完成 | + +P1-P4 可并行,P5-P6 顺序依赖。 + +--- + +## Phase 1: freelance Layer 1 — 智能业务引擎 + +**目标文件:** `crates/erp-plugin-freelance/plugin.toml` + +### Task 1.1: 新增 `[settings]` 段落 + +在 `[[numbering]]` 之前插入 7 个配置项: + +```toml +[settings] + + [[settings.fields]] + name = "company_name" + display_name = "公司名称" + field_type = "text" + required = true + group = "基本信息" + + [[settings.fields]] + name = "currency_symbol" + display_name = "货币符号" + field_type = "text" + default_value = "¥" + group = "基本信息" + + [[settings.fields]] + name = "default_tax_rate" + display_name = "默认税率(%)" + field_type = "number" + default_value = 6 + range = [0.0, 100.0] + group = "财务" + + [[settings.fields]] + name = "payment_reminder_days" + display_name = "收款提前提醒(天)" + field_type = "number" + default_value = 3 + range = [1.0, 30.0] + group = "提醒" + + [[settings.fields]] + name = "notify_contract_expiring" + display_name = "合同到期提醒" + field_type = "boolean" + default_value = true + group = "提醒" + + [[settings.fields]] + name = "notify_payment_overdue" + display_name = "逾期收款提醒" + field_type = "boolean" + default_value = true + group = "提醒" + + [[settings.fields]] + name = "notify_opportunity_followup" + display_name = "商机跟进提醒" + field_type = "boolean" + default_value = true + group = "提醒" +``` + +### Task 1.2: 新增 `[[trigger_events]]` 段落 + +在 `[settings]` 之后插入 5 个触发事件: + +```toml +[[trigger_events]] +name = "opportunity_stage_changed" +display_name = "商机阶段变更" +description = "商机阶段发生变化时通知,特别是成交或失败" +entity = "opportunity" +on = "update" + +[[trigger_events]] +name = "contract_status_changed" +display_name = "合同状态变更" +description = "合同状态变化时检查到期预警" +entity = "contract" +on = "update" + +[[trigger_events]] +name = "invoice_status_changed" +display_name = "发票状态变更" +description = "发票状态变化时检查逾期收款" +entity = "invoice" +on = "update" + +[[trigger_events]] +name = "task_status_changed" +display_name = "任务状态变更" +description = "任务完成或取消时通知" +entity = "task" +on = "update" + +[[trigger_events]] +name = "expense_created" +display_name = "新支出记录" +description = "记录新支出时通知" +entity = "expense" +on = "create" +``` + +### Task 1.3: 追加 cascade 属性(5 处已有字段) + +**1.3a** contract.opportunity_id(第 450-454 行)追加: +```toml + cascade_from = "client_id" + cascade_filter = "client_id" +``` + +**1.3b** contract.quote_id(第 456-460 行)追加: +```toml + cascade_from = "client_id" + cascade_filter = "client_id" +``` + +**1.3c** invoice.project_id(第 796-800 行)追加: +```toml + cascade_from = "client_id" + cascade_filter = "client_id" +``` + +**1.3d** invoice.contract_id(第 802-806 行)追加: +```toml + cascade_from = "client_id" + cascade_filter = "client_id" +``` + +**1.3e** time_entry.task_id(第 745-751 行)追加: +```toml + cascade_from = "project_id" + cascade_filter = "project_id" +``` + +### Task 1.4: 追加 visible_when 属性(4 处已有字段) + +**1.4a** invoice.payment_date(第 860-863 行)追加: +```toml + visible_when = "status == 'paid' || status == 'partial'" +``` + +**1.4b** contract.paid_amount(第 516-520 行)追加: +```toml + visible_when = "status != 'drafting'" +``` + +**1.4c** task.actual_hours(第 727-730 行)追加: +```toml + visible_when = "status != 'todo'" +``` + +**1.4d** quote.total_amount(第 357-361 行)追加: +```toml + visible_when = "status != 'draft'" +``` + +### Task 1.5: 追加 validation 属性(2 处已有字段) + +**1.5a** client.phone(第 135-138 行)追加: +```toml + validation = { pattern = "^1[3-9]\\d{9}$", message = "请输入有效的手机号" } +``` + +**1.5b** client.email(第 140-143 行)追加: +```toml + validation = { pattern = "^[^@]+@[^@]+\\.[^@]+$", message = "请输入有效的邮箱地址" } +``` + +### Task 1.6: 编译 WASM + 升级插件 + +```bash +cargo build -p erp-plugin-freelance --target wasm32-unknown-unknown --release +wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_freelance.wasm -o target/erp_plugin_freelance.component.wasm +``` + +通过 API 升级: +```bash +# 上传新版本 WASM +curl -X POST http://localhost:3000/api/v1/admin/plugins/{plugin_id}/upgrade \ + -H "Authorization: Bearer {token}" \ + -F "wasm=@target/erp_plugin_freelance.component.wasm" \ + -F "manifest=@crates/erp-plugin-freelance/plugin.toml" +``` + +### Task 1.7: 验证 + +- [ ] `cargo check` 通过 +- [ ] 重新登录获取新 JWT(权限可能变化) +- [ ] 前端打开 freelance 插件 → 设置页面可见 7 个配置项 +- [ ] 创建客户 → phone 格式错误时提示校验信息 +- [ ] 创建客户 → email 格式错误时提示校验信息 +- [ ] 创建合同 → 选客户后 opportunity_id 和 quote_id 自动过滤 +- [ ] 创建发票 → 选客户后 project_id 和 contract_id 自动过滤 +- [ ] 创建工时 → 选项目后 task_id 自动过滤 +- [ ] invoice 状态为 pending 时,payment_date 字段不显示 +- [ ] contract 状态为 drafting 时,paid_amount 字段不显示 +- [ ] 触发事件:更新商机阶段 → 消息中心收到通知 +- [ ] `git add && git commit && git push` + +--- + +## Phase 2: itops Layer 1 — 智能业务引擎 + +**目标文件:** `crates/erp-plugin-itops/plugin.toml` + +### Task 2.1: 新增 `[settings]` 段落 + +在 `[[numbering]]` 之前插入 4 个配置项: + +```toml +[settings] + + [[settings.fields]] + name = "default_sla_response" + display_name = "默认SLA响应时间(小时)" + field_type = "number" + default_value = 8 + range = [1.0, 72.0] + group = "SLA" + + [[settings.fields]] + name = "default_sla_resolve" + display_name = "默认SLA解决时间(小时)" + field_type = "number" + default_value = 48 + range = [1.0, 168.0] + group = "SLA" + + [[settings.fields]] + name = "notify_sla_breach" + display_name = "SLA超标提醒" + field_type = "boolean" + default_value = true + group = "提醒" + + [[settings.fields]] + name = "notify_check_due" + display_name = "巡检到期提醒" + field_type = "boolean" + default_value = true + group = "提醒" +``` + +### Task 2.2: 新增 `[[trigger_events]]` 段落 + +在 `[settings]` 之后插入 4 个触发事件: + +```toml +[[trigger_events]] +name = "ticket_created" +display_name = "新工单" +description = "创建工单时开始SLA计时并通知" +entity = "ticket" +on = "create" + +[[trigger_events]] +name = "ticket_status_changed" +display_name = "工单状态变更" +description = "工单状态变化时检查SLA是否达标" +entity = "ticket" +on = "update" + +[[trigger_events]] +name = "contract_status_changed" +display_name = "维保合同状态变更" +description = "合同状态变化时检查到期预警" +entity = "service_contract" +on = "update" + +[[trigger_events]] +name = "check_plan_updated" +display_name = "巡检计划更新" +description = "巡检计划更新时检查下次巡检日期" +entity = "check_plan" +on = "update" +``` + +### Task 2.3: 追加 cascade 属性(2 处已有字段) + +**2.3a** ticket.contract_id(第 186-192 行)追加: +```toml + cascade_from = "client_id" + cascade_filter = "client_id" +``` + +**2.3b** check_record.contract_id(第 398-400 行)追加: +```toml + cascade_from = "plan_id" + cascade_filter = "contract_id" +``` + +### Task 2.4: 追加 visible_when 属性(6 处已有字段) + +**2.4a** ticket.resolution → `visible_when = "status == 'resolved' || status == 'closed'"` +**2.4b** ticket.responded_at → `visible_when = "status != 'open'"` +**2.4c** ticket.resolved_at → `visible_when = "status == 'resolved' || status == 'closed'"` +**2.4d** ticket.closed_at → `visible_when = "status == 'closed'"` +**2.4e** check_record.issues_found → `visible_when = "result == 'abnormal'"` +**2.4f** check_record.actions_taken → `visible_when = "result == 'abnormal'"` + +### Task 2.5: 追加 validation 属性(1 处已有字段) + +**2.5a** service_contract.contract_number(第 73-78 行)追加: +```toml + validation = { pattern = "^SC-\\d{4}-\\d{4}$", message = "格式:SC-YYYY-NNNN" } +``` + +### Task 2.6: 编译 WASM + 升级插件 + +```bash +cargo build -p erp-plugin-itops --target wasm32-unknown-unknown --release +wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_itops.wasm -o target/erp_plugin_itops.component.wasm +``` + +### Task 2.7: 验证 + +- [ ] `cargo check` 通过 +- [ ] 重新登录获取新 JWT +- [ ] 前端打开 itops 插件 → 设置页面可见 4 个配置项 +- [ ] 创建工单 → 选客户后 contract_id 自动过滤 +- [ ] 工单状态为 open 时,resolution/resolved_at/closed_at 不显示 +- [ ] 工单状态改为 resolved → resolution 和 resolved_at 出现 +- [ ] 巡检记录结果为 normal → issues_found/actions_taken 不显示 +- [ ] 巡检记录结果改为 abnormal → issues_found/actions_taken 出现 +- [ ] 触发事件:创建工单 → 消息中心收到通知 +- [ ] `git add && git commit && git push` + +--- + +## Phase 3: freelance Layer 3 — PDF 模板 + +**目标文件:** `crates/erp-plugin-freelance/plugin.toml` + +### Task 3.1: 新增 `[[templates]]` 段落(3 个模板) + +在 `[[ui.pages]]` 之前插入报价单、发票、合同 3 个 PDF 模板。 + +**报价单模板** (`quote_pdf`): +- entity = "quote" +- 包含 Handlebars 语法:`{{quote_number}}`, `{{client.name}}`, `{{#each lines}}` +- 表格渲染:item_name / description / quantity / unit_price / amount +- 底部:subtotal / tax_rate / total_amount + +**发票模板** (`invoice_pdf`): +- entity = "invoice" +- grid 布局:client.name / type / issue_date / due_date +- 大字金额:`¥{{amount}}` +- 状态 badge + +**合同模板** (`contract_pdf`): +- entity = "contract" +- 签章区域:甲方/乙方 +- parties 区块:client.name / amount / paid_amount / 期限 / payment_terms + +### Task 3.2: 编译 WASM + 升级 + +同 Task 1.6 流程。 + +### Task 3.3: 验证 + +- [ ] 前端打开报价单详情 → 可见"生成 PDF"按钮 +- [ ] 点击生成 → 下载 PDF,内容包含正确的字段值 +- [ ] 发票 PDF → 金额/客户名正确 +- [ ] 合同 PDF → 签章区域正确 +- [ ] `git add && git commit && git push` + +--- + +## Phase 4: itops Layer 3 — 维保合同 PDF 模板 + +**目标文件:** `crates/erp-plugin-itops/plugin.toml` + +### Task 4.1: 新增 `[[templates]]` 段落(1 个模板) + +维保合同模板 (`service_contract_pdf`): +- entity = "service_contract" +- SLA 承诺框:响应/解决时间 +- grid 布局:client.name / amount / 期限 / status +- 服务范围 / 付款条款 / 签章区 + +### Task 4.2: 编译 WASM + 升级 + +同 Task 2.6 流程。 + +### Task 4.3: 验证 + +- [ ] 前端打开维保合同详情 → 可见"生成 PDF"按钮 +- [ ] 点击生成 → 下载 PDF,SLA 承诺正确 +- [ ] `git add && git commit && git push` + +--- + +## Phase 5: 平台 dashboard widgets 扩展 + +> **注意:** 此阶段需要修改平台 Rust 代码 + 前端代码,不是纯 plugin.toml 改动。 + +### Task 5.1: 扩展 manifest.rs — 定义 PluginWidget 类型 + +**目标文件:** `crates/erp-plugin/src/manifest.rs` + +在 `PluginPageType::Dashboard` 结构体中新增 `widgets` 字段: + +```rust +// PluginPageType::Dashboard 新增字段 +Dashboard { + label: String, + #[serde(default)] + icon: Option, + #[serde(default)] + widgets: Option>, // 新增 +}, +``` + +定义 `PluginWidget` 枚举及其子类型: + +```rust +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum PluginWidget { + StatCards { + label: String, + cards: Vec, + }, + ActionList { + label: String, + #[serde(default)] + max_items: Option, + queries: Vec, + }, + Funnel { + label: String, + entity: String, + lane_field: String, + #[serde(default)] + value_field: Option, + lane_order: Vec, + }, + CardList { + label: String, + entity: String, + #[serde(default)] + filter: Option, + #[serde(default)] + max_items: Option, + title_field: String, + #[serde(default)] + subtitle_field: Option, + #[serde(default)] + tags: Vec, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StatCard { + pub entity: String, + #[serde(default)] + pub aggregate: Option, // count, sum + #[serde(default)] + pub field: Option, + #[serde(default)] + pub filter: Option, + pub label: String, + #[serde(default)] + pub icon: Option, + #[serde(default)] + pub color: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActionQuery { + pub entity: String, + #[serde(default)] + pub filter: Option, + #[serde(default)] + pub sort: Option, + pub label_field: String, + #[serde(default)] + pub subtitle_field: Option, + pub action: String, + #[serde(default)] + pub icon: Option, +} +``` + +### Task 5.2: 扩展插件 API — 返回 widgets 数据 + +**目标文件:** `crates/erp-plugin/src/module.rs` + +新增 API 端点,为 dashboard widgets 提供数据: + +- `GET /api/v1/plugins/{plugin_id}/dashboard/widgets` — 返回 widgets 定义 +- `GET /api/v1/plugins/{plugin_id}/dashboard/data` — 返回 widgets 聚合数据(调用已有 count/aggregate API) + +### Task 5.3: 前端渲染 dashboard widgets + +**目标目录:** `apps/web/src/` + +新增组件: +- `PluginDashboard.tsx` — 仪表盘容器,读取 widgets 定义并渲染 +- `StatCardsWidget.tsx` — 统计卡片组件(4 个指标卡片) +- `ActionListWidget.tsx` — 待办列表组件 +- `FunnelWidget.tsx` — 漏斗图组件 +- `CardListWidget.tsx` — 卡片列表组件 + +### Task 5.4: 验证 + +- [ ] `cargo check` 通过 +- [ ] 前端 `pnpm build` 通过 +- [ ] manifest.rs 正确解析 widgets TOML +- [ ] API 返回 widgets 定义和聚合数据 +- [ ] `git add && git commit && git push` + +--- + +## Phase 6: freelance + itops Layer 2 — 仪表盘 widgets + +> **前置:** Phase 5 完成(平台支持 widgets) + +### Task 6.1: freelance — 替换仪表盘页面为 widgets 版本 + +**目标文件:** `crates/erp-plugin-freelance/plugin.toml` + +将现有的空仪表盘(第 949-952 行)替换为包含 4 个 widgets 的完整仪表盘: +1. stat_cards — 财务概览(4 张卡片) +2. action_list — 紧急待办(4 种查询) +3. funnel — 商机漏斗 +4. card_list — 活跃项目 + +### Task 6.2: itops — 新增仪表盘页面到最前面 + +**目标文件:** `crates/erp-plugin-itops/plugin.toml` + +在现有页面列表最前面插入仪表盘页面(2 个 widgets): +1. stat_cards — 运维概览(4 张卡片) +2. action_list — 紧急待办(3 种查询) + +### Task 6.3: 两个插件各自编译 WASM + 升级 + +### Task 6.4: 验证 + +- [ ] freelance 仪表盘 → 4 个 widget 正确渲染 +- [ ] itops 仪表盘 → 2 个 widget 正确渲染 +- [ ] 财务卡片数值正确(调用 aggregate API) +- [ ] 紧急待办列表有数据时显示条目 +- [ ] 商机漏斗按阶段显示金额分布 +- [ ] `git add && git commit && git push` + +--- + +## 执行策略 + +**P1-P4 并行策略:** P1 和 P2 可以同时开始(不同文件),P3 和 P4 在 P1/P2 完成后立即跟进。每个 Phase 独立编译 WASM、独立验证、独立提交。 + +**P5-P6 顺序策略:** P5 是平台改动(Rust + 前端),P6 依赖 P5 的平台能力才能生效。 + +**预估工作量:** +- P1: 30-40 分钟(plugin.toml 编辑 + 编译 + 验证) +- P2: 20-30 分钟(规模小于 P1) +- P3: 15-20 分钟(3 个模板插入) +- P4: 10-15 分钟(1 个模板插入) +- P5: 60-90 分钟(manifest 扩展 + API + 前端组件) +- P6: 20-30 分钟(plugin.toml widgets 声明)