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

16 KiB
Raw Blame History

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 个配置项:

[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 个触发事件:

[[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 行)追加:

  cascade_from = "client_id"
  cascade_filter = "client_id"

1.3b contract.quote_id第 456-460 行)追加:

  cascade_from = "client_id"
  cascade_filter = "client_id"

1.3c invoice.project_id第 796-800 行)追加:

  cascade_from = "client_id"
  cascade_filter = "client_id"

1.3d invoice.contract_id第 802-806 行)追加:

  cascade_from = "client_id"
  cascade_filter = "client_id"

1.3e time_entry.task_id第 745-751 行)追加:

  cascade_from = "project_id"
  cascade_filter = "project_id"

Task 1.4: 追加 visible_when 属性4 处已有字段)

1.4a invoice.payment_date第 860-863 行)追加:

  visible_when = "status == 'paid' || status == 'partial'"

1.4b contract.paid_amount第 516-520 行)追加:

  visible_when = "status != 'drafting'"

1.4c task.actual_hours第 727-730 行)追加:

  visible_when = "status != 'todo'"

1.4d quote.total_amount第 357-361 行)追加:

  visible_when = "status != 'draft'"

Task 1.5: 追加 validation 属性2 处已有字段)

1.5a client.phone第 135-138 行)追加:

  validation = { pattern = "^1[3-9]\\d{9}$", message = "请输入有效的手机号" }

1.5b client.email第 140-143 行)追加:

  validation = { pattern = "^[^@]+@[^@]+\\.[^@]+$", message = "请输入有效的邮箱地址" }

Task 1.6: 编译 WASM + 升级插件

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 升级:

# 上传新版本 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 个配置项:

[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 个触发事件:

[[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 行)追加:

  cascade_from = "client_id"
  cascade_filter = "client_id"

2.3b check_record.contract_id第 398-400 行)追加:

  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 行)追加:

  validation = { pattern = "^SC-\\d{4}-\\d{4}$", message = "格式SC-YYYY-NNNN" }

Task 2.6: 编译 WASM + 升级插件

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 字段:

// PluginPageType::Dashboard 新增字段
Dashboard {
    label: String,
    #[serde(default)]
    icon: Option<String>,
    #[serde(default)]
    widgets: Option<Vec<PluginWidget>>,  // 新增
},

定义 PluginWidget 枚举及其子类型:

#[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 声明)