Files
erp/docs/superpowers/specs/2026-04-20-freelance-itops-plugin-enhancement-design.md
iven bcc6662add
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / security-audit (push) Has been cancelled
docs(spec): 修复 spec 审查问题 — cascade/validation 标注为修改已有字段,dashboard 标注平台依赖
- CRITICAL: 明确 dashboard widgets 需要平台 manifest.rs 扩展
- HIGH: cascade/validation/visible_when 均改为"已有字段追加属性"
- HIGH: visible_when 提供完整 TOML 语法
- MEDIUM: 模板引擎说明(Handlebars)和关系解析机制
- MEDIUM: itops validation 补充字段上下文
- MEDIUM: itops dashboard 明确插入位置
2026-04-20 00:50:29 +08:00

625 lines
20 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
> 来源: 多专家头脑风暴UX专家 + 业务顾问 + 运维专家 + 财务专家)
> 状态: Draft
> 前置: `docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md`
---
## 1. 背景与动机
当前插件是「数据录入系统」,不是「赚钱工具」。一人 IT 服务公司的核心痛点:
1. **钱从哪里来?** — 商机跟进靠人记,没有自动提醒、没有漏斗分析
2. **项目做到哪了?** — 任务状态和工时手动填,跟合同金额/应收款脱节
3. **钱收回来了吗?** — 报价→合同→开票→收款割裂,没有串联
4. **运维服务会不会忘?** — 巡检计划写了没人催SLA 超时了才知道
5. **税和利润算不清?** — 收支分散在不同表里,月底做账要手动汇总
**问题根因:** 平台已有 trigger_events、settings、templates、cascade_from、visible_when、validation 六大能力,但两个插件完全没有使用。
**改进目标:** 纯插件层增强,三层递进:
- Layer 1: 智能业务引擎 — 让系统主动驱动用户做事
- Layer 2: 仪表盘重构 — 一个页面掌控全局
- Layer 3: 专业输出 — 一键生成报价单/发票/合同 PDF
---
## 2. Layer 1: 智能业务引擎 — freelance 插件
### 2.1 Settings插件配置页
一次性配置公司信息和业务偏好,后续自动生效:
```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 = "提醒"
```
### 2.2 Trigger Events自动事件驱动
关键操作时自动发通知,把"人找事"变"事找人"
```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"
```
### 2.3 Cascade智能联动下拉
选客户后自动过滤其关联数据。以下均为**已有字段追加 cascade 属性**,不是新增字段:
**contract 实体 — 已有 opportunity_id 字段追加:**
```toml
cascade_from = "client_id"
cascade_filter = "client_id"
```
**contract 实体 — 已有 quote_id 字段追加:**
```toml
cascade_from = "client_id"
cascade_filter = "client_id"
```
**invoice 实体 — 已有 project_id 字段追加:**
```toml
cascade_from = "client_id"
cascade_filter = "client_id"
```
**invoice 实体 — 已有 contract_id 字段追加:**
```toml
cascade_from = "client_id"
cascade_filter = "client_id"
```
**time_entry 实体 — 已有 task_id 字段追加:**
```toml
cascade_from = "project_id"
cascade_filter = "project_id"
```
### 2.4 Visible When条件显示
只在有意义时才显示字段。以下为**已有字段追加 visible_when 属性**
**invoice 实体 — 已有 payment_date 字段追加:**
```toml
visible_when = "status == 'paid' || status == 'partial'"
```
**contract 实体 — 已有 paid_amount 字段追加:**
```toml
visible_when = "status != 'drafting'"
```
**task 实体 — 已有 actual_hours 字段追加:**
```toml
visible_when = "status != 'todo'"
```
**quote 实体 — 已有 total_amount 字段追加:**
```toml
visible_when = "status != 'draft'"
```
### 2.5 Validation字段校验
**已有字段追加 validation 属性**,不是新增字段:
**client 实体 — 已有 email 字段追加:**
```toml
validation = { pattern = "^[^@]+@[^@]+\\.[^@]+$", message = "请输入有效的邮箱地址" }
```
**client 实体 — 已有 phone 字段追加:**
```toml
validation = { pattern = "^1[3-9]\\d{9}$", message = "请输入有效的手机号" }
```
---
## 3. Layer 2: 仪表盘重构 — freelance 插件
将占位符仪表盘升级为真正的指挥中心。通过 `widgets` 声明告诉平台该展示什么。
> **平台依赖:** 仪表盘 widgets 需要平台层配合:
> 1. `manifest.rs` 的 `PluginPageType::Dashboard` 需要新增 `widgets: Option<Vec<PluginWidget>>` 字段
> 2. 定义 `PluginWidget` 枚举stat_cards/action_list/funnel/card_list 类型)
> 3. 更新 TOML 解析和验证逻辑
> 4. 前端解析 `widgets` 声明并渲染对应组件
>
> 因此 P5/P6 **不是纯 plugin.toml 改动**,需要平台+前端联合实施。以下 widgets 声明作为设计参考,实施时需先完成平台侧支持。
```toml
[[ui.pages]]
type = "dashboard"
label = "工作台"
icon = "DashboardOutlined"
# ── 财务概览卡片 ──
[[ui.pages.widgets]]
type = "stat_cards"
label = "财务概览"
cards = [
{ entity = "invoice", aggregate = "sum", field = "amount", filter = "type == 'payment' && status != 'overdue'", label = "本月收入", icon = "rise", color = "green" },
{ entity = "expense", aggregate = "sum", field = "amount", label = "本月支出", icon = "fall", color = "red" },
{ entity = "invoice", aggregate = "sum", field = "amount", filter = "status == 'overdue' || status == 'pending'", label = "应收总额", icon = "dollar", color = "orange" },
{ entity = "invoice", aggregate = "count", filter = "status == 'overdue'", label = "逾期笔数", icon = "warning", color = "red" }
]
# ── 紧急待办 ──
[[ui.pages.widgets]]
type = "action_list"
label = "紧急待办"
max_items = 5
queries = [
{ entity = "invoice", filter = "status == 'overdue'", label_field = "invoice_number", subtitle_field = "amount", action = "查看", icon = "warning" },
{ entity = "task", filter = "status != 'done' && status != 'cancelled'", sort = "due_date asc", label_field = "title", subtitle_field = "due_date", action = "处理", icon = "clock" },
{ entity = "contract", filter = "status == 'active'", sort = "end_date asc", label_field = "title", subtitle_field = "end_date", action = "续约", icon = "file-text" },
{ entity = "opportunity", filter = "next_follow_up <= today", label_field = "title", subtitle_field = "next_follow_up", action = "跟进", icon = "phone" }
]
# ── 商机漏斗 ──
[[ui.pages.widgets]]
type = "funnel"
label = "商机漏斗"
entity = "opportunity"
lane_field = "stage"
value_field = "estimated_amount"
lane_order = ["visit", "requirement", "quote", "negotiation", "won", "lost"]
# ── 活跃项目卡片 ──
[[ui.pages.widgets]]
type = "card_list"
label = "活跃项目"
entity = "project"
filter = "status == 'in_progress'"
max_items = 4
title_field = "name"
subtitle_field = "contract_amount"
tags = ["business_type", "status"]
```
**依赖:** 数据源来自平台已有的聚合 API`/count``/aggregate`。Filter 表达式使用平台过滤 DSL`==`, `!=`, `||`, `&&`, `<=`)。
---
## 4. Layer 3: 专业输出 — freelance 插件
一键生成专业 PDF替代手动排 Word。
> **模板引擎说明:**
> - 语法基于 Handlebars`{{field}}`, `{{#each relation}}...{{/each}}`
> - 当前实体字段直接可用:`{{amount}}`, `{{status}}`
> - 关系字段解析:`{{client.name}}` 表示通过 `client_id` 引用的 client 实体的 name 字段,渲染器需自动解析
> - `{{#each lines}}` 用于一对多关系(如 quote → quote_line渲染器查询子实体并遍历
> - 平台需要实现 PDF 渲染管道TOML 模板 → Handlebars 渲染(注入数据)→ HTML → wkhtmltopdf/浏览器打印 → PDF
### 4.1 报价单模板
```toml
[[templates]]
name = "quote_pdf"
display_name = "报价单"
entity = "quote"
format = "pdf"
template_html = """
<html>
<head><style>
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
h1 { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f5f5f5; }
.total { text-align: right; font-size: 18px; font-weight: bold; }
.footer { margin-top: 40px; color: #666; font-size: 12px; }
</style></head>
<body>
<h1>报价单 {{quote_number}}</h1>
<p>客户:{{client.name}} | 有效期至:{{valid_until}}</p>
<table>
<tr><th>项目</th><th>描述</th><th>数量</th><th>单价</th><th>金额</th></tr>
{{#each lines}}
<tr><td>{{item_name}}</td><td>{{description}}</td><td>{{quantity}}</td><td>{{unit_price}}</td><td>{{amount}}</td></tr>
{{/each}}
</table>
<p class="total">小计:{{subtotal}} | 税率:{{tax_rate}}% | 总计:{{total_amount}}</p>
<div class="footer">备注:{{notes}}</div>
</body>
</html>
"""
```
### 4.2 发票模板
```toml
[[templates]]
name = "invoice_pdf"
display_name = "发票"
entity = "invoice"
format = "pdf"
template_html = """
<html>
<head><style>
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
h1 { text-align: center; color: #1890ff; border-bottom: 2px solid #1890ff; padding-bottom: 10px; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 20px 0; }
.info-item { padding: 8px; background: #fafafa; }
.amount { font-size: 24px; font-weight: bold; text-align: center; color: #f5222d; margin: 20px 0; }
.status-badge { display: inline-block; padding: 4px 12px; border-radius: 4px; background: #f0f0f0; }
</style></head>
<body>
<h1>发票 {{invoice_number}}</h1>
<div class="info-grid">
<div class="info-item">客户:{{client.name}}</div>
<div class="info-item">类型:{{type}}</div>
<div class="info-item">开票日期:{{issue_date}}</div>
<div class="info-item">到期日:{{due_date}}</div>
</div>
<div class="amount">¥{{amount}}</div>
<p>状态:<span class="status-badge">{{status}}</span></p>
<p>备注:{{notes}}</p>
</body>
</html>
"""
```
### 4.3 合同模板
```toml
[[templates]]
name = "contract_pdf"
display_name = "合同"
entity = "contract"
format = "pdf"
template_html = """
<html>
<head><style>
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
h1 { text-align: center; border-bottom: 3px double #333; padding-bottom: 10px; }
.parties { margin: 20px 0; padding: 15px; background: #fafafa; border-left: 4px solid #1890ff; }
.signature { margin-top: 60px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
.sig-box { border-top: 1px solid #333; padding-top: 10px; text-align: center; }
</style></head>
<body>
<h1>{{title}}</h1>
<p>合同编号:{{contract_number}}</p>
<div class="parties">
<p>甲方:{{client.name}}</p>
<p>合同金额:¥{{amount}} | 已付:¥{{paid_amount}}</p>
<p>期限:{{start_date}} 至 {{end_date}}</p>
<p>付款条款:{{payment_terms}}</p>
</div>
<p>备注:{{notes}}</p>
<div class="signature">
<div class="sig-box">甲方签章</div>
<div class="sig-box">乙方签章</div>
</div>
</body>
</html>
"""
```
---
## 5. itops 插件增强
### 5.1 Settings
```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 = "提醒"
```
### 5.2 Trigger Events
```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"
```
### 5.3 Cascade
**已有字段追加 cascade 属性**,不是新增字段:
**ticket 实体 — 已有 contract_id 字段追加:**
```toml
cascade_from = "client_id"
cascade_filter = "client_id"
```
**check_record 实体 — 已有 contract_id 字段追加:**
```toml
cascade_from = "plan_id"
cascade_filter = "contract_id"
```
### 5.4 Visible When
**已有字段追加 visible_when 属性**
**ticket 实体 — 已有 resolution 字段追加:**
```toml
visible_when = "status == 'resolved' || status == 'closed'"
```
**ticket 实体 — 已有 responded_at 字段追加:**
```toml
visible_when = "status != 'open'"
```
**ticket 实体 — 已有 resolved_at 字段追加:**
```toml
visible_when = "status == 'resolved' || status == 'closed'"
```
**ticket 实体 — 已有 closed_at 字段追加:**
```toml
visible_when = "status == 'closed'"
```
**check_record 实体 — 已有 issues_found 字段追加:**
```toml
visible_when = "result == 'abnormal'"
```
**check_record 实体 — 已有 actions_taken 字段追加:**
```toml
visible_when = "result == 'abnormal'"
```
### 5.5 Validation
**已有字段追加 validation 属性**
**service_contract 实体 — 已有 contract_number 字段追加:**
```toml
validation = { pattern = "^SC-\\d{4}-\\d{4}$", message = "格式SC-YYYY-NNNN" }
```
### 5.6 Dashboard
> **同 Layer 2 说明:** widgets 需要平台层配合manifest.rs 扩展 + 前端渲染),非纯 plugin.toml 改动。此仪表盘页面**插入到现有页面列表最前面**,现有 4 个页面保持不变。
```toml
[[ui.pages]]
type = "dashboard"
label = "运维概览"
icon = "DashboardOutlined"
[[ui.pages.widgets]]
type = "stat_cards"
label = "运维概览"
cards = [
{ entity = "service_contract", aggregate = "count", filter = "status == 'active'", label = "活跃合同", icon = "file-text", color = "blue" },
{ entity = "ticket", aggregate = "count", filter = "status == 'open' || status == 'in_progress'", label = "待处理工单", icon = "tool", color = "orange" },
{ entity = "ticket", aggregate = "count", filter = "status == 'resolved'", label = "已解决工单", icon = "check-circle", color = "green" },
{ entity = "check_plan", aggregate = "count", filter = "status == 'active'", label = "活跃巡检", icon = "schedule", color = "blue" }
]
[[ui.pages.widgets]]
type = "action_list"
label = "紧急待办"
max_items = 5
queries = [
{ entity = "ticket", filter = "status == 'open'", sort = "priority asc", label_field = "title", subtitle_field = "type", action = "处理", icon = "warning" },
{ entity = "service_contract", filter = "status == 'active'", sort = "end_date asc", label_field = "name", subtitle_field = "end_date", action = "续约", icon = "file-text" },
{ entity = "check_plan", filter = "status == 'active'", sort = "next_check_date asc", label_field = "name", subtitle_field = "next_check_date", action = "巡检", icon = "schedule" }
]
```
### 5.7 Template维保合同 PDF
```toml
[[templates]]
name = "service_contract_pdf"
display_name = "维保合同"
entity = "service_contract"
format = "pdf"
template_html = """
<html>
<head><style>
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
h1 { text-align: center; border-bottom: 3px double #1890ff; padding-bottom: 10px; color: #1890ff; }
.sla-box { margin: 20px 0; padding: 15px; background: #e6f7ff; border: 1px solid #91d5ff; border-radius: 4px; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 20px 0; }
.info-item { padding: 8px; background: #fafafa; }
.signature { margin-top: 60px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
.sig-box { border-top: 1px solid #333; padding-top: 10px; text-align: center; }
</style></head>
<body>
<h1>{{name}}</h1>
<p>合同编号:{{contract_number}}</p>
<div class="info-grid">
<div class="info-item">客户:{{client.name}}</div>
<div class="info-item">合同金额:¥{{amount}}</div>
<div class="info-item">期限:{{start_date}} 至 {{end_date}}</div>
<div class="info-item">状态:{{status}}</div>
</div>
<div class="sla-box">
<strong>SLA 承诺:</strong>响应 {{sla_response_hours}} 小时内 / 解决 {{sla_resolve_hours}} 小时内
</div>
<p>服务范围:{{service_scope}}</p>
<p>付款条款:{{payment_terms}}</p>
<p>备注:{{notes}}</p>
<div class="signature">
<div class="sig-box">甲方签章</div>
<div class="sig-box">乙方签章</div>
</div>
</body>
</html>
"""
```
---
## 6. 改进汇总
| 层次 | 能力 | freelance | itops |
|------|------|-----------|-------|
| Layer 1 | settings | 7 个配置项(公司名/税率/提醒偏好) | 4 个配置项SLA默认值/提醒偏好) |
| Layer 1 | trigger_events | 5 个事件(商机/合同/发票/任务/支出) | 4 个事件(工单/合同/巡检) |
| Layer 1 | cascade | 4 处联动(合同/发票/工时表单) | 2 处联动(工单/巡检记录) |
| Layer 1 | visible_when | 4 个条件字段 | 6 个条件字段 |
| Layer 1 | validation | 2 个校验(邮箱/手机) | 1 个校验(合同编号格式) |
| Layer 2 | dashboard widgets | 财务卡片+紧急待办+商机漏斗+项目卡片 | 运维卡片+紧急待办 |
| Layer 3 | templates | 3 个 PDF报价单/发票/合同) | 1 个 PDF维保合同 |
**总计:** 2 个插件 × 3 层增强,从「数据录入」升级为「赚钱工具」。
---
## 7. 实施优先级
```
P1: freelance Layer 1settings + trigger_events + cascade + visible_when + validation
P2: itops Layer 1settings + trigger_events + cascade + visible_when + validation
P3: freelance Layer 33 个 PDF 模板)
P4: itops Layer 3维保合同 PDF 模板)
P5: freelance Layer 2仪表盘 widgets
P6: itops Layer 2仪表盘 widgets
```
P1-P4 是纯 plugin.toml 改动(给已有字段追加 cascade/visible_when/validation 属性,以及新增 settings/trigger_events/templates 段落可立即实施。P5-P6 的仪表盘 widgets 需要平台层配合:扩展 `manifest.rs``PluginPageType::Dashboard` 支持 `widgets` 字段 + 前端渲染组件。