Files
erp/plans/fluffy-painting-bachman.md
iven 841766b168
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
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

243 lines
8.8 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.

# CRM 插件平台标杆 — P0 基础能力设计
## Context
CRM 插件作为 ERP 平台的第一个行业插件,目前暴露了插件平台的多项基础能力缺口。本次设计的定位不是"CRM 功能最全",而是"插件平台能力最扎实"——通过 CRM 验证的每个能力都应被所有未来插件inventory、生产、财务等零改动复用。
对标一流 CRMSalesforce/HubSpot/Pipedrive的差距分析表明当前 CRM 更接近"客户通讯录+标签+图谱",距离可用 CRM 有显著差距。但这些差距中,最优先补的是**平台基础设施**,而非 CRM 业务功能。
## 设计决策记录
| 决策点 | 选择 | 理由 |
|--------|------|------|
| 目标定位 | 插件平台标杆 | 打磨通用能力,所有插件受益 |
| 推进节奏 | P0 基础先行 | 基础不扎实,上层的业务功能无法可靠运行 |
| 实体关系复杂度 | 全覆盖 (1:N/N:1/N:N/自引用) | CRM 和 inventory 都需要,一次到位 |
| 字段校验范围 | 完整套件 (6种) | 数据质量是所有插件的生命线 |
| 前端硬编码 | 全部通用化 | 第二个插件必须零改动可用 |
## P0-1: 实体关系声明 + ref_entity + 级联策略
### Manifest Schema 扩展
在 entity 下新增 `[[relations]]` 段:
```toml
[[schema.entities.relations]]
name = "contacts" # 关系名
target_entity = "contact" # 目标实体
type = "one_to_many" # one_to_many | many_to_one | many_to_many
foreign_key = "customer_id" # FK 字段
on_delete = "cascade_soft_delete" # cascade_soft_delete | set_null | restrict
display_field = "name" # 下拉框显示字段
# N:N 需要中间表
[[schema.entities.relations]]
name = "related_customers"
target_entity = "customer"
type = "many_to_many"
through_entity = "customer_relationship"
through_source_field = "from_customer_id"
through_target_field = "to_customer_id"
```
### 后端实现 (crates/erp-plugin/)
**关键文件:**
- `src/manifest.rs` — ManifestParser 新增 relations 解析
- `src/dynamic_table.rs` — 安装时存储关系到 entity metadata
- `src/data_service.rs` — 删除时执行级联策略,创建/更新时验证 FK
- `src/handler/data_handler.rs` — 错误响应格式
**级联策略执行流程:**
```
DELETE /plugins/{id}/{entity}/{rid}
→ 查询 entity 的所有 incoming relations (被引用的关系)
→ for each relation:
cascade_soft_delete → UPDATE child SET deleted_at=now() WHERE fk=rid
set_null → UPDATE child SET fk=NULL WHERE fk=rid
restrict → SELECT COUNT children, if >0 return 409 Conflict
→ 软删除目标记录
```
**FK 存在性校验:**
```
POST/PUT /plugins/{id}/{entity}
→ for each field with ref_entity:
SELECT EXISTS(SELECT 1 FROM plugin_xxx_{ref_entity} WHERE id=field_value AND deleted_at IS NULL)
→ 不存在则返回 400 + 具体字段错误
```
### 前端实现 (apps/web/)
**关键文件:**
- `src/pages/PluginCRUDPage.tsx` — 自动为 ref_entity 字段渲染 EntitySelect
- `src/pages/PluginDetailPage` — 自动渲染关联子实体内嵌列表
- `src/components/EntitySelect.tsx` — 增强支持 display_field 配置
- `src/api/plugins.ts` — schema 类型新增 relations
**详情页自动关联渲染:**
- 读取 entity 的 outgoing relations (one_to_many)
- 为每个 relation 渲染内嵌 CRUD 表格compact 模式,带 filter=fk:parent_id
- 级联删除前弹出确认("将同时删除 3 条联系人"
### CRM plugin.toml 改造
为 5 个实体补充 relations 声明:
- customer → contacts (1:N, cascade_soft_delete)
- customer → communications (1:N, cascade_soft_delete)
- customer → tags (1:N, cascade_soft_delete)
- customer → parent (N:1, set_null, 自引用)
- contact → communications (1:N, cascade_soft_delete)
---
## P0-2: 字段校验层
### Manifest Schema 扩展
在 field 下新增 `[validation]` 子结构:
```toml
[[schema.entities.fields]]
name = "phone"
field_type = "string"
[schema.entities.fields.validation]
pattern = "^1[3-9]\\d{9}$"
message = "请输入有效的手机号码"
min_length = 11
max_length = 11
[[schema.entities.fields]]
name = "email"
field_type = "string"
[schema.entities.fields.validation]
pattern = "^[\\w.-]+@[\\w.-]+\\.\\w+$"
message = "请输入有效的邮箱地址"
max_length = 254
```
### 后端校验器 (crates/erp-plugin/src/validation.rs — 新文件)
6 种校验器统一执行:
| 校验器 | 触发条件 | 错误格式 |
|--------|---------|---------|
| required | `field.required = true` | `{field}: 不能为空` |
| unique | `field.unique = true` | `{field}: 该值已存在` |
| pattern | `validation.pattern` regex match | `{field}: {validation.message}` |
| ref_exists | `field.ref_entity` FK 查询 | `{field}: 引用的{entity}不存在` |
| min_length / max_length | `validation.min_length / max_length` | `{field}: 长度必须在 {min}-{max} 之间` |
| min_value / max_value | `validation.min_value / max_value` | `{field}: 值必须在 {min}-{max} 之间` |
**执行位置:** `data_service.rs` 的 create/update 方法中,数据写入前统一调用。
**错误响应:**
```json
{
"success": false,
"error": "数据验证失败",
"details": [
{ "field": "phone", "message": "请输入有效的手机号码" },
{ "field": "customer_id", "message": "引用的客户不存在" }
]
}
```
### 前端校验生成
从 schema 自动生成 Ant Design Form rules
- `required``{ required: true, message: "..." }`
- `pattern``{ pattern: /regex/, message: "..." }`
- `min_length / max_length``{ min: n, max: n, message: "..." }`
### CRM plugin.toml 补充校验
- phone: pattern 手机号
- email: pattern 邮箱
- credit_code: pattern 统一社会信用代码 (18位)
- website: pattern URL
- customer_id: ref_entity = "customer" (FK 校验)
---
## P0-3: 前端去硬编码
### Dashboard 通用化
**文件:** `apps/web/src/pages/dashboard/dashboardConstants.tsx`, `PluginDashboardPage.tsx`
改造方案:
- 移除 `ENTITY_COLORS``ENTITY_ICONS` 硬编码映射
- 颜色自动分配: 8 色调色板按 entity 顺序循环
- 图标从 page schema 的 icon 字段读取
- 标题: `{manifest.name} 统计概览`,副标题: `{manifest.description}`
- Widget 定义从 page schema 的 widgets 数组读取
### Graph 通用化
**文件:** `apps/web/src/pages/plugins/graph/graphConstants.ts`
改造方案:
- 移除 `RELATIONSHIP_COLORS` 硬编码
- 关系类型标签从 field.options 读取 (已有 label 映射)
- 颜色用调色板按 option 顺序循环分配
- 未知类型 fallback 到灰色 + 原始 label
### CRUD 表格列可配置
**文件:** `PluginCRUDPage.tsx`
改造方案:
- manifest page 新增 `table_columns: ["name", "customer_type", "level", "status", "owner_id"]`
- 不声明则默认取前 8 个非 hidden 非 FK 字段
- 移除 `fields.slice(0, 5)` 硬编码
### 验证标准
> 换成 inventory 插件Dashboard/Graph/CRUD 应该零改动正确渲染。
---
## 关键文件清单
| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `crates/erp-plugin/src/manifest.rs` | 修改 | 新增 relations + validation 解析 |
| `crates/erp-plugin/src/validation.rs` | 新建 | 校验引擎 |
| `crates/erp-plugin/src/data_service.rs` | 修改 | 集成级联策略 + 校验 |
| `crates/erp-plugin/src/dynamic_table.rs` | 修改 | 安装时存储关系元数据 |
| `crates/erp-plugin/src/handler/data_handler.rs` | 修改 | FK 校验错误格式 |
| `crates/erp-plugin-crm/plugin.toml` | 修改 | 补充 relations + validation |
| `apps/web/src/pages/dashboard/dashboardConstants.tsx` | 修改 | 去硬编码,通用调色板 |
| `apps/web/src/pages/dashboard/DashboardWidgets.tsx` | 修改 | schema 驱动 |
| `apps/web/src/pages/PluginDashboardPage.tsx` | 修改 | 通用标题/副标题 |
| `apps/web/src/pages/plugins/graph/graphConstants.ts` | 修改 | 关系类型从 options 读取 |
| `apps/web/src/pages/PluginCRUDPage.tsx` | 修改 | 可配置列数 |
| `apps/web/src/api/plugins.ts` | 修改 | 类型定义更新 |
---
## 验证方案
1. **编译检查**: `cargo check` 全 workspace 通过
2. **单元测试**: validation.rs 每种校验器独立测试
3. **集成测试**: Testcontainers 验证级联删除/FK 校验/unique 冲突
4. **功能验证**:
- 重新安装 CRM 插件,确认 5 个 relation 正确注册
- 删除客户 → 联系人/沟通记录/标签级联软删除
- 创建联系人 → customer_id 不存在时返回 400
- 手机号/邮箱格式不正确时返回校验错误
- Dashboard 切换 inventory 插件时正确渲染
5. **前端验证**: `pnpm dev` 启动后手动测试所有页面
---
## 输出产物
1. 设计规格文档: `docs/superpowers/specs/2026-04-18-crm-plugin-platform-p0-design.md`
2. 实施计划: 通过 writing-plans skill 生成
3. 知识库文档: 记录讨论过程和决策理由