背景: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;真实浏览器六页验证通过。
127 lines
8.0 KiB
Markdown
127 lines
8.0 KiB
Markdown
# 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/`(三套候选,待用户选择)
|