Files
erp/docs/superpowers/specs/2026-04-20-freelance-itops-plugin-enhancement-design.md
iven f4afc969bd
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(spec): 自由职业者/IT运维插件增强设计规格
三层增强方案:智能业务引擎 + 仪表盘重构 + 专业输出
纯插件层实现,无需修改平台代码
2026-04-20 00:43:34 +08:00

610 lines
19 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智能联动下拉
选客户后自动过滤其关联数据:
**contract 实体新增:**
```toml
# client_id 选择后opportunity_id 自动过滤只显示该客户的商机
[[schema.entities.fields]]
name = "opportunity_id"
field_type = "uuid"
display_name = "关联商机"
ref_entity = "opportunity"
cascade_from = "client_id"
cascade_filter = "client_id"
# client_id 选择后quote_id 自动过滤只显示该客户的报价
[[schema.entities.fields]]
name = "quote_id"
field_type = "uuid"
display_name = "关联报价"
ref_entity = "quote"
cascade_from = "client_id"
cascade_filter = "client_id"
```
**invoice 实体新增:**
```toml
# client_id 选择后project_id 自动过滤
[[schema.entities.fields]]
name = "project_id"
field_type = "uuid"
display_name = "关联项目"
ref_entity = "project"
cascade_from = "client_id"
cascade_filter = "client_id"
# client_id 选择后contract_id 自动过滤
[[schema.entities.fields]]
name = "contract_id"
field_type = "uuid"
display_name = "关联合同"
ref_entity = "contract"
cascade_from = "client_id"
cascade_filter = "client_id"
```
**time_entry 实体新增:**
```toml
# project_id 选择后task_id 自动过滤只显示该项目的任务
[[schema.entities.fields]]
name = "task_id"
field_type = "uuid"
display_name = "关联任务"
ui_widget = "entity_select"
ref_entity = "task"
ref_label_field = "title"
ref_search_fields = ["title"]
cascade_from = "project_id"
cascade_filter = "project_id"
```
### 2.4 Visible When条件显示
只在有意义时才显示字段:
| 实体 | 字段 | 条件 |
|------|------|------|
| invoice | payment_date | `status == 'paid' \|\| status == 'partial'` |
| contract | paid_amount | `status != 'drafting'` |
| task | actual_hours | `status != 'todo'` |
| quote | total_amount | `status != 'draft'` |
### 2.5 Validation字段校验
```toml
# client 实体 - email 字段增加校验
[[schema.entities.fields]]
name = "email"
field_type = "string"
display_name = "邮箱"
validation = { pattern = "^[^@]+@[^@]+\\.[^@]+$", message = "请输入有效的邮箱地址" }
# client 实体 - phone 字段增加校验
[[schema.entities.fields]]
name = "phone"
field_type = "string"
display_name = "电话"
validation = { pattern = "^1[3-9]\\d{9}$", message = "请输入有效的手机号" }
```
---
## 3. Layer 2: 仪表盘重构 — freelance 插件
将占位符仪表盘升级为真正的指挥中心。通过 `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"]
```
**依赖:** 仪表盘 widgets 渲染需要前端配合解析 `widgets` 声明。数据源来自平台已有的聚合 API`/count``/aggregate`)。
---
## 4. Layer 3: 专业输出 — freelance 插件
一键生成专业 PDF替代手动排 Word
### 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
```toml
# ticket 实体:选客户后过滤其维保合同
[[schema.entities.fields]]
name = "contract_id"
field_type = "uuid"
display_name = "维保合同"
ui_widget = "entity_select"
ref_entity = "service_contract"
ref_label_field = "name"
ref_search_fields = ["name"]
cascade_from = "client_id"
cascade_filter = "client_id"
# check_record 实体:选巡检计划后过滤合同
[[schema.entities.fields]]
name = "contract_id"
field_type = "uuid"
display_name = "维保合同"
ref_entity = "service_contract"
cascade_from = "plan_id"
cascade_filter = "contract_id"
```
### 5.4 Visible When
| 实体 | 字段 | 条件 |
|------|------|------|
| ticket | resolution | `status == 'resolved' \|\| status == 'closed'` |
| ticket | responded_at | `status != 'open'` |
| ticket | resolved_at | `status == 'resolved' \|\| status == 'closed'` |
| ticket | closed_at | `status == 'closed'` |
| check_record | issues_found | `result == 'abnormal'` |
| check_record | actions_taken | `result == 'abnormal'` |
### 5.5 Validation
```toml
# service_contract - contract_number 格式校验
validation = { pattern = "^SC-\\d{4}-\\d{4}$", message = "格式SC-YYYY-NNNN" }
```
### 5.6 Dashboard
```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 改动可立即实施。P5-P6 的仪表盘 widgets 需要前端配合解析 widgets 声明。