docs(crm): add product brand and alias design spec.

Capture the agreed model: per-product brands, document-line brand constraints, archive-time brand quantities, and brand-level inventory.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-27 21:42:53 +08:00
1 parent 768ef934f9
commit d7e0edf2d5
1 file changed
+220
@@ -0,0 +1,220 @@
# 产品品牌与别名设计(含运输计划归档按品牌记账)
> 日期:2026-07-27
> 状态:待用户审阅
> 范围:web-admin CRM 产品 / 报价 / 合同 + PL 运输计划归档与库存
> 选定方案:方案 1 — 约定在单据行,实绩在归档拆品牌
## 1. 背景与目标
### 1.1 现状
- CRM 管理下已有「产品管理」(`web-admin/src/views/crm/product/index.vue`,路由 `crm/product`)。
- `crm_product` 无品牌、别名字段;部分产品把品牌写进名称(如「片碱(君正)」「纯碱(红四方)」)。
- 报价 / 合同 / 计划明细均快照 `product_name`:`crm_quote_product` → `crm_contract_product` → `pl_plan_product`。
- 库存 `pl_inventory` 以 `product_id` 唯一,出入库流水无品牌维度。
### 1.2 业务诉求
1. **品牌**:采购有品牌;报价/合同可「指定品牌」或「不限品牌」。不限时可混品牌发货;指定则按该品牌发货。
2. **别名**:客户希望合同展示名与内部标准名不同(如内部「烧碱」、合同写「氢氧化钠」),对内仍是同一产品。
3. **库存**:本期即按品牌记库存、出入库与盘点;运输计划**归档时**,有品牌的产品按品牌分别填写入库量 / 出库量。
### 1.3 非目标(首期不做)
- 全局品牌字典、客户专属别名
- 计划创建阶段按品牌拆行
- 库位 / 批次 / 完整 WMS
- 地磅或并列 CRM 仓库联动
- 线索 / 商机意向产品结构化品牌
- 多套复杂打印模板(仅在现有模板增加品牌字段)
## 2. 决策摘要
| 议题 | 决策 |
|------|------|
| 产品 vs 品牌 | 一条产品多品牌,不按品牌拆成多条产品 |
| 品牌主数据 | 各产品独立维护品牌列表,无全局品牌字典 |
| 别名 | 挂在产品上;开单时选用标准名或别名写入行快照 |
| 品牌约定落点 | 报价 / 合同行:`指定` / `不限` |
| 实际品牌落点 | 运输计划归档时按品牌填量 |
| 库存粒度 | `产品 + 品牌`;与归档实绩一起做 |
| 出库库存不足 | 首期强校验拦截 |
## 3. 数据模型
### 3.1 产品品牌子表 `crm_product_brand`
| 字段 | 说明 |
|------|------|
| id | UUID 主键 |
| product_id | 所属产品 |
| brand_name | 品牌名称 |
| status | 启用 / 停用(或等价字段) |
| order_num 等公共字段 | 按 database-conventions |
- 子表为空 = 无品牌产品。
- 品牌仅属于该产品。
### 3.2 产品别名子表 `crm_product_alias`
| 字段 | 说明 |
|------|------|
| id | UUID 主键 |
| product_id | 所属产品 |
| alias_name | 别名 |
| 公共字段 | 按规范 |
- 开单可选:标准 `product_name` 或任一别名,选中值写入单据行 `product_name` 快照。
### 3.3 报价 / 合同行扩展
表:`crm_quote_product`、`crm_contract_product` 增加:
| 字段 | 说明 |
|------|------|
| brand_mode | `required` 指定品牌 / `any` 不限品牌;无品牌产品可不写或空 |
| brand_id | 指定时有值;不限为空 |
| brand_name | 指定时的品牌名快照 |
| product_name | 现有字段,存对外展示名(标准名或选用别名) |
合同从报价生成时上述字段一并拷贝。
### 3.4 计划行扩展 `pl_plan_product`
增加同样的 `brand_mode` / `brand_id` / `brand_name`(从合同带入)。
计划创建 / 编辑阶段**不按品牌拆行**,仅展示约束。
### 3.5 归档品牌实绩 `pl_plan_product_brand_qty`
| 字段 | 说明 |
|------|------|
| plan_product_id | 计划产品行 |
| brand_id / brand_name | 实际品牌 |
| inbound_qty / outbound_qty | 按计划类型使用对应字段 |
规则:
- `brand_mode=required`:仅允许该品牌一行有量。
- `brand_mode=any`:可多品牌多行;有量合计 = 该产品行应归档总量。
- 产品无品牌:不写本表,沿用行上原入/出库量字段。
### 3.6 库存
- `pl_inventory`:唯一键改为 `(tenant_no, product_id, brand_id)`;无品牌使用空品牌键(`brand_id=''`)。
- `pl_inventory_transaction`:增加 `brand_id` / `brand_name`。
- 预警按「产品 + 品牌」维度维护。
## 4. 业务流转
```text
产品维护品牌/别名
→ 报价行:展示名 + 品牌约定(指定/不限)
→ 合同行:快照拷贝
→ 计划行:带入约束(不拆行)
→ 归档:按品牌填入/出库量
→ 按品牌写库存流水与台账
```
### 4.1 产品管理
- 维护品牌列表、别名列表(均可空)。
- 有品牌 → 后续单据才出现品牌控件。
- 有别名 → 开单可选展示名。
### 4.2 报价
1. 选产品,带出标准属性。
2. 有别名:选择展示名 → 写入行 `product_name`。
3. 有品牌:选择「指定品牌」(必选品牌)或「不限品牌」。
4. 打印/导出:指定显示品牌;不限显示「不限品牌」或不印具体品牌(全系统统一一种文案)。
### 4.3 合同
- 从报价带入含展示名与品牌约定。
- 手工加行交互同报价。
- 已落库快照不随主数据更名/改品牌而变。
### 4.4 运输计划
- 展示产品名 + 品牌约束(指定品牌名或「不限」)。
- 不在此阶段选择实际发货品牌,不扣库存。
### 4.5 归档
| 情况 | 填写方式 |
|------|----------|
| 无品牌 | 现有单行填量 |
| 指定品牌 | 仅该品牌一行填量 |
| 不限品牌 | 按该产品品牌列表分行填量;合计必须等于应归档总量 |
校验失败不可完成归档。
### 4.6 库存记账
- 归档成功后按品牌明细生成 inbound/outbound 流水并更新台账。
- 台账默认「产品 + 品牌」;支持按产品汇总。
- 出库:某品牌填写量 > 该品牌库存 → **强校验拦截**。
- 入库:不拦截。
## 5. 界面要点
对齐 Fresh Mint 与现有 CRM/PL 列表、弹窗模式。
- **产品**:弹窗内品牌、别名可编辑列表;列表可展示品牌摘要列。
- **报价/合同行**:有别名则可选展示名;有品牌则「指定/不限」+ 指定时品牌下拉。默认建议「不限品牌」。
- **计划**:增加品牌约束列。
- **归档**:产品行下展开按品牌填量;合计校验。
- **库存台账 / 流水 / 入出库单**:展示品牌;无品牌显示「—」。
## 6. 校验与主数据变更
- 指定品牌必须选自该产品启用中的品牌。
- 无品牌产品不得出现品牌控件。
- 删除品牌:存在库存或未归档计划引用时禁止删除(可先停用)。
- 停用品牌:新单据不可选;在途单据按快照执行。
- 改名/改别名/删品牌不回写历史报价、合同、计划快照。
## 7. 历史数据迁移
1. 现有 `pl_inventory` 一行/产品 → 迁移为 `brand_id=''` 的无品牌库存行;唯一键切换前完成。
2. 名称含「(品牌)」的产品:提供清洗策略(拆标准名 + 写入 `crm_product_brand`);**旧单据文字快照保持不变**。
3. 预警阈值:原产品级阈值落到无品牌行,或由业务在上线后按品牌重设。
## 8. 影响模块清单
| 模块 | 调整要点 |
|------|----------|
| CRM 产品管理 | 品牌/别名子表 CRUD、UI |
| 报价单 | 行字段、选择交互、打印 |
| 合同 | 行字段、从报价拷贝、打印 |
| 运输计划 | 约束展示、从合同带入 |
| 计划归档 | 按品牌填量 UI + 校验 + 触发库存 |
| 库存台账/流水/预警 | 唯一键与维度升级 |
| 入库单/出库单 | 明细展示实际品牌 |
| 公共组件/API | 产品选择、字段透传、类型定义 |
| docs/sql | 表结构与迁移脚本 |
## 9. 测试要点
- 无品牌产品:全链路与现网行为一致。
- 指定品牌:报价→合同→计划展示正确;归档只能填该品牌;出库扣对应品牌库存。
- 不限品牌:归档多品牌分摊;合计校验;库存分品牌增减。
- 别名:行上展示名正确;主数据标准名不变;历史单据不回写。
- 库存不足出库归档被拦截;入库正常。
- 停用/删除品牌的边界行为。
- 旧库存迁移后台账可查、可继续出入库。
## 10. 实现分期建议
可在同一变更内按任务拆分,但上线应具备库存维度升级 + 归档落账,避免「有品牌约定却仍按产品总量记账」的中间态长期存在。
建议任务序:
1. DDL:子表 + 单据行字段 + 归档品牌量表 + 库存唯一键迁移
2. 产品 API/UI(品牌、别名)
3. 报价/合同 API/UI + 打印字段
4. 计划带入与展示
5. 归档按品牌填量 + 库存记账改造
6. 台账/流水/入出库单展示与回归
7. 历史名称清洗(可并行,需业务确认)