Files
erp/docs/superpowers/plans/2026-04-20-freelance-itops-plugin-enhancement-plan.md
iven 7e063a7e88
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
docs(plan): freelance/itops 插件增强实施计划 — P1-P6 六阶段
P1-P4 纯 plugin.toml(settings/trigger_events/cascade/visible_when/validation/templates)
P5 平台 manifest.rs + 前端 widgets 扩展
P6 两个插件仪表盘 widgets 声明
2026-04-20 07:14:30 +08:00

588 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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"按钮
- [ ] 点击生成 → 下载 PDFSLA 承诺正确
- [ ] `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<String>,
#[serde(default)]
widgets: Option<Vec<PluginWidget>>, // 新增
},
```
定义 `PluginWidget` 枚举及其子类型:
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PluginWidget {
StatCards {
label: String,
cards: Vec<StatCard>,
},
ActionList {
label: String,
#[serde(default)]
max_items: Option<u32>,
queries: Vec<ActionQuery>,
},
Funnel {
label: String,
entity: String,
lane_field: String,
#[serde(default)]
value_field: Option<String>,
lane_order: Vec<String>,
},
CardList {
label: String,
entity: String,
#[serde(default)]
filter: Option<String>,
#[serde(default)]
max_items: Option<u32>,
title_field: String,
#[serde(default)]
subtitle_field: Option<String>,
#[serde(default)]
tags: Vec<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatCard {
pub entity: String,
#[serde(default)]
pub aggregate: Option<String>, // count, sum
#[serde(default)]
pub field: Option<String>,
#[serde(default)]
pub filter: Option<String>,
pub label: String,
#[serde(default)]
pub icon: Option<String>,
#[serde(default)]
pub color: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionQuery {
pub entity: String,
#[serde(default)]
pub filter: Option<String>,
#[serde(default)]
pub sort: Option<String>,
pub label_field: String,
#[serde(default)]
pub subtitle_field: Option<String>,
pub action: String,
#[serde(default)]
pub icon: Option<String>,
}
```
### 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 声明)