# CRM 插件平台标杆 — P0 基础能力设计 ## Context CRM 插件作为 ERP 平台的第一个行业插件,目前暴露了插件平台的多项基础能力缺口。本次设计的定位不是"CRM 功能最全",而是"插件平台能力最扎实"——通过 CRM 验证的每个能力都应被所有未来插件(inventory、生产、财务等)零改动复用。 对标一流 CRM(Salesforce/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. 知识库文档: 记录讨论过程和决策理由