Files
lingxi/docs/adr/ADR-002-两级行业目录.md
Stev_Wang 140dc386c1 feat(client): A3 客户与行业目录(两级行业、客户组、字段审批)
背景:A3 需把行业 / 客户 / 客户组 / 客户级字段立为资产根节点;用户确认两级行业树与混搭 UI(行业 B / 客户列表 A / 详情 A / 新建 B / 客户组 B / 字段 A)。

改动:
- 新增 ADR-002,行业改为两级树,客户挂末级行业;同步 PRD / 技术架构 / 开发计划。
- 新增 client 模块:industries / customers / customer-groups / customer-fields 接口、真实 PrismaCustomerDirectory、客户状态机与客户转交、客户组数据范围解析、跨客户 404。
- 新增迁移与幂等种子(电商/金融两级行业、2 客户、1 客户组、4 条字段)。
- 前端新增行业/客户/客户组/字段审批页面与路由、导航更名(客户管理 / 行业管理 / 客户组),复用现有 token。
- 补充 shared 契约单测、client 集成用例、A3 浏览器 e2e。

影响面:client 模块与 client 表、shared 契约、web 路由/导航、ScopeContext 数据范围、OpenAPI 与种子;A1 客户转交占位由 client 实现替换。
回滚:回滚本提交;数据库保留新增表或按报告执行 down SQL;CUSTOMER_DIRECTORY 可切回空集实现。
验证:format:check / lint / typecheck / test 全绿(shared 131 / api 90 / web 125,共 346);test:int 5 文件 39 条;A3 e2e 1/1;build 8/8;真实浏览器六页验证通过。
2026-11-03 21:45:38 +08:00

127 lines
8.0 KiB
Markdown
Raw Permalink 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.
# ADR-002 行业目录:由单层改为两级树,客户挂末级行业
| 项 | 内容 |
| --- | --- |
| 状态 | 已接受(2026-11-03,用户确认 A3 口径) |
| 决策人 | 产品 / 技术负责人(用户) |
| 记录人 | 工程 Lead |
| 关联 | 开发计划 V0.1 §6.4 A3、§7.4、§12.1 M0 DoD、附录 C;PRD V1.6 §5.4.2 / §5.4.5、R-20;技术架构 V0.4 §5.3 / §5.4.1 / §6.3.2;ADR-001 |
| 影响范围 | `client` 模块、`industry` 模块(行业包绑定)、`catalog` / `script` / `media` / `asset` 的行业维度、数据范围与统计报表、A4 字典口径、开发计划与 PRD/架构文档 |
## 背景
PRD R-20 与技术架构 §5.3 均把 `industries` 描述为**单层行业目录**(「行业 = 客户的业务属性」,名称全局唯一),「行业二级(电商 → 服饰鞋包 / 美妆个护)」在 PRD §5.4.5 中作为 A4 字典项处理;开发计划 §6.4 A3-BE-01 也按单层 `industries` 描述。
A3 开卡前用户确认:行业目录需要**两级树**(一级行业 → 二级行业),客户归属到末级行业。该口径与既有文档冲突,且影响客户归属、行业包匹配、数据范围、统计与 A4 字典边界,属于 AGENTS §0.4 要求先提 ADR 的决策。
## 决策
### D1 行业为两级树,最多两级
`industries` 表增加:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `parent_id` | ulid FK → industries,可空 | 一级行业为空;二级行业指向一级行业 |
| `level` | smallint | 1 / 2;由服务端按 `parent_id` 推导并校验 |
| `sort_order` | int | 同级排序,默认 10 |
| `status` | varchar | 启用 / 停用 |
| `remark` | varchar(200) | 备注 |
约束:
- 仅允许两级;二级行业下不可再建子行业(应用层校验 + 数据库可选触发器兜底)。
- 一级行业 `parent_id IS NULL AND level = 1`;二级行业 `parent_id NOT NULL AND level = 2`。
- 客户只挂末级行业,详见 D2。
### D2 客户挂末级行业,行业包按祖先回退
- 客户 `industry_id` 指向**末级行业**:有子行业的一级行业不可被客户选择;一级行业无子行业时可直接选择。
- 客户创建后不可跨行业移动;调整行业走「停用 + 新建」(PRD R-20 原口径不变)。
- 行业包匹配按「精确行业 → 一级行业 → 默认」回退(B0 落地);行业顾问/行业指南同样按「二级 → 一级 → default → 内置准则」匹配(PRD §5.4.7 / §7.x)。
- 行业级资产包、行业级字段覆盖等按「客户所属末级行业 → 一级行业」继承。
### D3 行业名称唯一性:一级全局唯一 + 二级同父唯一
- 一级行业名称全局唯一。
- 二级行业名称在同一 `parent_id` 下唯一;不同一级行业下允许重名(如「其他」)。
- 客户名称仍在同一末级行业下唯一(`(industry_id, name)` 唯一,原口径不变)。
### D4 删除与停用:只停用 + 引用保护
- 删除:无子行业、无客户、无资产时才可删除;否则只能停用。
- 停用:有启用中的子行业时禁止停用,提示先处理子级;有客户时允许停用,停用后新建客户不可选该行业,已有客户数据不受影响。
- 停用一级行业不级联停用子级;需逐级处理,避免一次操作影响面过大。
### D5 权限复用现有 30 个权限点
A3 行业管理不新增权限点:
- 查看:`customer:read`;写(新建 / 编辑 / 停用 / 删除):`customer:manage`。
- 开发计划附录 C 写的 `industry_pack:view` 在 A2 定稿的 30 个权限点中不存在,按本 ADR 回写为 `customer:read`;`industry:manage` 仍表示行业包编辑(B0)。
### D6 「行业二级」从 A4 字典中移除
PRD §5.4.5 的「行业二级」字典项改由 `industries` 两级树承载;A4 字典保留其他受控词表。A4-BE-01 的「三层合并(本行业 + 父行业 + 通用)」中的「行业作用域」按两级树的祖先链解释。
## 备选方案
| 方案 | 结论 | 原因 |
| --- | --- | --- |
| 保持单层行业(PRD/架构原文) | 未采用 | 用户明确要求两级;单层无法表达「电商 → 服饰鞋包」的行业分类 |
| 单层落地 + 预留 `parent_id` | 未采用 | 本期 UI 与客户选择就要两级联动,预留字段会造成前后端口径分叉 |
| 行业二级放 A4 字典 | 未采用 | 字典项没有行业归属/状态/引用保护,且客户 `industry_id` 无法稳定指向二级;A3 就需要行业树维护 |
| **两级树 + 客户挂末级(本 ADR)** | 采用 | 与用户口径一致;行业包按祖先回退,保留单层时期的继承语义 |
## 后果
### 正面
- 行业分类可表达一级/二级,客户归属更精确;行业包可在任一级配置并向下继承。
- 行业二级不再依赖 A4 字典,A3 即可交付完整行业树维护。
- 权限点不新增,复用 A2 定稿目录,治理成本低。
### 负面 / 代价
- 与 PRD R-20、技术架构 `industries`、开发计划 A3/A4/M0 DoD 的原文冲突,必须同步修订,否则文档与实现不一致。
- 客户归属校验从「行业存在」升级为「行业存在且为末级」,A2 `CustomerDirectory` 与数据范围解析需按末级行业返回客户。
- 行业统计、报表、行业包匹配需处理祖先链,查询复杂度上升。
- 历史数据若已按单层行业写入(当前本地库无行业数据),需 `parent_id` 置空并标记为一级行业;A3 首次建表,不涉及存量迁移。
### 迁移 / 兼容策略
A3 首次创建 `industries` 表即按两级结构落地,采用 expand → migrate → contract:
1. 建表含 `parent_id` / `level` / `sort_order` / `status` / `remark`;
2. 迁移脚本对存量行业(如有)设 `parent_id = NULL, level = 1`;
3. 应用层先写 `level`,后续若需严格约束再补触发器 / 检查约束;
4. 回滚:删除新增列即可回到单层语义(客户 `industry_id` 仍指向原行业)。
## 待办 / 回写清单
| 文件 | 章节 | 需要改成什么 |
| --- | --- | --- |
| `docs/PRD/灵犀-信息流广告素材一站式生产平台PRD.md` | R-20 | 明确行业两级树、客户挂末级、一级全局唯一 / 二级同父唯一 |
| 同上 | §5.4.2 | 客户所属行业改为末级行业;补充两级树与停用/删除规则 |
| 同上 | §5.4.5 | 「行业二级」从字典表移除,注明由 `industries` 两级树承载 |
| 同上 | §5.4.7 / §7.x 行业指南 | 明确「二级 → 一级 → default」回退 |
| `docs/技术架构/灵犀_信息流素材生产平台_技术架构_V0.1.md` | §5.3 表清单 | `industries` 增加 `parent_id` / `level`,约束改为「最多两级、一级全局唯一、二级同父唯一」 |
| 同上 | §5.4.1 `customers` | `industry_id` 指向末级行业;补充「客户不可挂有子行业的一级行业」 |
| 同上 | §6.3.2 | 行业接口权限改为 `customer:read` / `customer:manage`;补充两级树与删除/停用校验 |
| `docs/开发计划/灵犀_信息流素材生产平台_开发计划_V0.1.md` | §6.4 A3-BE-01 | 改为两级行业树;删除/停用规则按 ADR-002 D4 |
| 同上 | §7.4 | 页面口径补充「一级/二级树、客户挂末级」 |
| 同上 | §12.1 M0 DoD | 「行业两级」改为「行业两级树(一级 → 二级),客户挂末级」 |
| 同上 | 附录 C | `/system/industries` 权限改为 `customer:read` / `customer:manage` |
| `docs/开发计划/灵犀_A2角色权限与数据范围_实施报告_V0.1.md` | §5 遗留 1 | 注明 A3 接入真实客户目录时按末级行业返回客户 |
| `README.md` / `AGENTS.md` | 文档索引 / 事实基线 | 视需要补充 ADR-002 与行业口径变更 |
> 文档同步按 AGENTS §0.4「先改文档,再改代码」执行;本 ADR 先于 A3 后端实现落地。
## 关联
- [ADR-001 权限点目录与六类数据范围](ADR-001-权限点目录与六类数据范围.md)
- 开发计划 V0.1 §6.4 A3、§7.4、§12.1
- PRD V1.6 R-20、§5.4.2、§5.4.5
- 技术架构 V0.4 §5.3、§5.4.1、§6.3.2
- 前端原型:`design/a3-ui-v1/`(三套候选,待用户选择)