378 lines
22 KiB
Markdown
378 lines
22 KiB
Markdown
# 产品族(SPU)合并 — 后端设计
|
||
|
||
- 日期:2026-08-28(同日修订:价格允许人工覆盖、支持人工添加自定义商品入族;公开接口零新增全复用;admin 前端纳入本期)
|
||
- 状态:已评审(讨论稿定稿,待实施计划)
|
||
- 分支:`feature/product-link-merge-yeuimu`
|
||
- 范围:后端(apps/api)+ 后台管理前端(apps/admin);官网(apps/website)不在本期,
|
||
但公开接口保持 100% 兼容复用(见 §10.2)
|
||
|
||
## 1. 背景与问题
|
||
|
||
SDS(InkPOD)上游会为同一个实体商品下发多条近似链接,名称格式为
|
||
`国家(物流备注)品名-SKU-工艺位置[-仓库名]`,例如
|
||
`美国(包邮)180g纯棉T恤成人款-DG001-单面印花-美西洛杉矶一仓`。
|
||
一个"DG001 180G纯棉T恤(JSA002)"分类下存在约 18 条这样的链接。
|
||
|
||
现有系统的合并能力落在商品层(`goods.origin_good_id` 主源 + `good_origin_goods` 副源),
|
||
但合并语义很浅:
|
||
|
||
- 变体列表简单拼接,无归因、不去重;
|
||
- 商品详情、尺码表、包装规则、价格**只取主源**,副源独有的大尺码(如 XXXXL)直接丢失;
|
||
- 物流、工艺只存在于链接名的自由文本中,无法结构化查询;
|
||
- 同一个族若按国家配置多个 Good,需要重复合并、重算并集。
|
||
|
||
## 2. 目标 / 非目标
|
||
|
||
**目标**
|
||
|
||
1. 在原产品库引入"产品族(ProductFamily)"实体:多个 OriginGood 链接合并为一个族;
|
||
2. 族的共享属性:规范名称、主图、国家、分类、商品详情;
|
||
3. 尺码表与包装规则做**并集**(S~XXXL ∪ S~XXXXXL),冲突有明确裁决规则;
|
||
4. 价格完全由数据推导:`价格 = f(尺码, 颜色, 物流, 工艺, 印花数量)`,其中印花数量
|
||
即链接工艺段自带的属性(单面/双面印花等),**无订单量折扣、无人工定价**;
|
||
5. 官网买家可对五个维度全部做选择,价格实时联动;
|
||
6. SDS 同步流水线保持"纯镜像"定位不变。
|
||
|
||
**非目标**
|
||
|
||
- 不引入价格阶梯/折扣表(订单量定价已被明确否决);价格默认按链接推导透传,
|
||
但**允许人工按格改价(覆盖表)**,并支持**人工添加自定义商品入族**(见 §5.5、§5.6、§8);
|
||
- 不改动同步任务的核心抓取逻辑与安全护栏;
|
||
- 不改动 apps/website(官网)——公开接口零新增、原样复用,官网可无感灰度。
|
||
|
||
## 3. 决策记录
|
||
|
||
| # | 问题 | 决策 |
|
||
| --- | --- | --- |
|
||
| D1 | 合并落在哪一层 | **新增 SPU 产品族层**(介于 OriginGood 与 Good 之间),而非强化 Good 级合并或读取时动态聚合 |
|
||
| D2 | 价格形态 | **默认按链接透传 + 人工覆盖**(2026-08-28 修订):无阶梯折扣、无全手动明码矩阵;价格默认 = 成员链接变体的 SDS 原价,允许按格子人工改价(覆盖表),并允许人工添加自定义商品入族 |
|
||
| D3 | 买家选价粒度 | **五维全选**:详情页提供尺码/颜色/工艺(印花数量)/物流选择器,价格实时联动 |
|
||
| D4 | 公开接口 | **零新增、全复用**:不新建任何公开端点,族数据以增量字段嵌入既有 `/public/*` 响应;`goodId=sdsGoodId` 与"任意成员命中同一族"语义不变 |
|
||
| D5 | 前端范围 | **admin 后台前端纳入本期**(原产品树按族分组、族管理、人工改价、自定义商品四块界面);官网不做 |
|
||
|
||
## 4. 架构
|
||
|
||
```
|
||
SDS 同步(不动) 产品族层(新增,人工策展) 运营配置层(调整)
|
||
OriginGood #1 ─┐ ┌── ProductFamily ─────────────┐ Good
|
||
OriginGood #2 ─┼─ familyId ──────►│ 规范名/主图/国家/分类 │──────► 新增引用族(familyId)
|
||
...(18条) │ (自动预填, │ 商品详情(canonical) │ 保留 origin_good_id
|
||
OriginGood #18 ┘ 可人工调整) │ 并集尺码表 + 并集包装规则(物化)│ 国家/坑位/标签/优先级不变
|
||
│ 价格矩阵(推导物化,零人工) │
|
||
└──────────────────────────────┘
|
||
```
|
||
|
||
核心原则:
|
||
|
||
- `OriginGood` 保持纯 SDS 镜像,同步流水线不改;仅在详情同步提交后追加"重算受影响族"的钩子;
|
||
- 族是详情、尺码、包装、价格的唯一事实来源;
|
||
- 族上 `autoManaged=true` 时随上游自动重算,`false` 时人工锁定、只置 stale 标记。
|
||
|
||
## 5. 数据模型(Prisma 草案)
|
||
|
||
### 5.1 新增 `ProductFamily`
|
||
|
||
```prisma
|
||
model ProductFamily {
|
||
id BigInt @id @default(autoincrement()) @map("family_id")
|
||
familyCode String? @map("family_code") // 如 DG001 / JSA002,从链接名 SKU 段提取,可人工改
|
||
familyName String @map("family_name") // 规范名,如 180g纯棉T恤(成人款)
|
||
familyImage String? @map("family_image")
|
||
countryId BigInt? @map("country_id") // 关联现有 Country
|
||
categoryId BigInt? @map("category_id") // 关联现有 Category
|
||
detail Json? // canonical 详情(文本字段默认取主链接,可人工编辑)
|
||
sizeChart Json? @map("size_chart") // 物化并集
|
||
packageSpecs Json? @map("package_specs") // 物化并集
|
||
priceMatrix Json? @map("price_matrix") // 物化推导结果(重算快照,非人工数据)
|
||
autoManaged Boolean @default(true) @map("auto_managed")
|
||
stale Boolean @default(false) // 锁定族在上游变化后的待处理标记
|
||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||
|
||
originGoods OriginGood[]
|
||
goods Good[]
|
||
country Country? @relation(fields: [countryId], references: [id])
|
||
category Category? @relation(fields: [categoryId], references: [id])
|
||
|
||
@@unique([familyCode])
|
||
@@index([countryId])
|
||
@@index([categoryId])
|
||
@@map("product_families")
|
||
}
|
||
```
|
||
|
||
### 5.2 `OriginGood` 增量
|
||
|
||
```prisma
|
||
familyId BigInt? @map("family_id") // 归属族,族删除时置空
|
||
skuCode String? @map("sku_code") // 名称第2段,如 DG001
|
||
logisticsLabel String? @map("logistics_label") // 第1段括号内物流备注,如 包邮/专线
|
||
craftLabel String? @map("craft_label") // 第3段工艺位置,如 单面印花(含印花数量语义)
|
||
warehouseLabel String? @map("warehouse_label") // 第4段(可选)仓库,如 美西洛杉矶一仓
|
||
|
||
@@index([familyId]) @@index([skuCode]) @@index([logisticsLabel]) @@index([craftLabel])
|
||
```
|
||
|
||
这四个解析列由同步落库时自动填充(见 §6),是价格归因、自动建族、按维度查询的共同地基。
|
||
解析失败的链接列置空,不阻塞同步,进后台待处理列表。
|
||
|
||
### 5.3 `Good` 增量
|
||
|
||
```prisma
|
||
familyId BigInt? @map("family_id") // 新事实来源;保留 origin_good_id 作为主链接(跳转/兜底)
|
||
@@index([familyId])
|
||
```
|
||
|
||
`good_origin_goods` 关联表在迁移完成后只读保留一段时间,确认无回归后废弃删除。
|
||
|
||
### 5.4 价格矩阵 JSON 结构(物化快照)
|
||
|
||
```jsonc
|
||
{
|
||
"crafts": ["单面印花", "双面印花"], // 族内去重后的工艺(印花数量)选项
|
||
"logistics": ["包邮", "专线"], // 族内去重后的物流选项
|
||
"rows": [
|
||
{
|
||
"sizeId": "size_3", "sizeName": "XXXL",
|
||
"colorId": "color_2", "colorName": "黑色",
|
||
"craft": "单面印花", "logistics": "包邮",
|
||
"price": "25.00", // 有效价(Decimal 字符串):推导价或人工覆盖价
|
||
"manual": false, // true = 该格价格来自 FamilyPriceOverride
|
||
"sources": [ // 同格子的全部来源(后台核对下单路由用)
|
||
{ "sdsGoodId": "123", "sdsVariantId": "456", "price": "25.00" }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 5.5 价格覆盖表(人工改价)
|
||
|
||
```prisma
|
||
model FamilyPriceOverride {
|
||
id BigInt @id @default(autoincrement()) @map("family_price_override_id")
|
||
familyId BigInt @map("family_id")
|
||
sizeId String @map("size_id") // 精确格,不做通配
|
||
colorId String @map("color_id")
|
||
craft String @map("craft")
|
||
logistics String @map("logistics")
|
||
price Decimal @db.Decimal(12, 2) // 人工价,仅校验为正数,不限制方向
|
||
note String? // 改价原因备注(审计)
|
||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||
|
||
family ProductFamily @relation(fields: [familyId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([familyId, sizeId, colorId, craft, logistics])
|
||
@@map("family_price_overrides")
|
||
}
|
||
```
|
||
|
||
- 覆盖表**独立于推导矩阵**:重算只重建推导矩阵,覆盖表永不被动过;
|
||
- 有效价 = 覆盖命中 ? 覆盖价 : 推导价,物化时合并进 `priceMatrix`(命中行标记 `manual: true`);
|
||
- 覆盖可命中推导矩阵中**不存在**的格子(人工补组合/补价),但各维度取值必须来自该族
|
||
的选项集合(sizes/colors/crafts/logistics),校验失败返回 400;
|
||
- 删除覆盖即恢复推导价,无任何副作用。
|
||
|
||
### 5.6 自定义成员(人工添加商品)
|
||
|
||
复用现有 `OriginGood.source = CUSTOM` 机制(`POST /goods/custom` 已存在),扩展为可入族:
|
||
|
||
- 自定义 OriginGood 与 SDS 链接一样通过 `familyId` 挂族;其变体(尺码/颜色/价格)、
|
||
详情、尺码表、包装规则全部为人工数据;
|
||
- 四个解析列(skuCode/logistics/craft/warehouse)由管理员创建时**手工填写**,
|
||
其中物流、工艺为必填(价格矩阵归因依赖);
|
||
- 重算语义:自定义成员的变体与详情是**人工数据、只读贡献**——并集与矩阵推导时读取,
|
||
永不覆盖或删除;仅当管理员显式移出族或删除该商品时失效;
|
||
- 自动建族(3 段规则)不会把 CUSTOM 成员吸进族,归属完全由人工决定。
|
||
|
||
## 6. 链接名结构化解析
|
||
|
||
输入:`OriginGood.goodName`(`国家(物流备注)品名-SKU-工艺位置[-仓库名]`)。
|
||
|
||
输出:`{ country?, logisticsLabel?, productName?, skuCode?, craftLabel?, warehouseLabel? }`
|
||
|
||
- 按现有 3 段规则(`apps/admin/src/utils/origin-name.ts` 的语义)在后端以 TS 实现,
|
||
作为共享解析器(放 `apps/api/src/origin-goods/origin-name.parser.ts` 或 common),
|
||
admin 端后续改为复用同一语义;
|
||
- 解析时机:同步 upsert `OriginGood` 时、以及存量回填脚本;
|
||
- 容错:名称不符合格式时各列置空;解析器对真实数据(约 552 条,其中 541 条符合格式)需有
|
||
golden 样本测试,包括畸形样本。
|
||
|
||
## 7. 并集与冲突规则
|
||
|
||
- **尺码表并集**:按 `sizeName` 对齐合并各成员的 `sizeChart`;
|
||
同一尺码测量值冲突时,取**尺码覆盖最全的成员**的该行(即提供 XXXXL 的链接优先),
|
||
其次取主链接;无法对齐时记 warning 日志,供管理员在族编辑页人工裁决后锁定(`autoManaged=false`)。
|
||
- **包装规则并集**:同样按尺码对齐合并 `packageSpecs`,冲突规则同上。
|
||
- **商品详情**:canonical 版本默认取主链接(管理员可指定),并集只作用于尺码表与包装规则。
|
||
- **同格子价格冲突**(同物流+同工艺、不同仓库的链接,同尺码颜色不同价):取**最低价**,
|
||
`sources` 保留全部来源;在此基础上若存在人工覆盖,以覆盖价为最终有效价(§5.5)。
|
||
|
||
## 8. 价格模型
|
||
|
||
```
|
||
第一次:选链接 → 物流 + 工艺(印花数量) (一个组合对应一条/多条成员链接)
|
||
第二次:选变体 → 尺码 + 颜色 (链接下的 SDS 变体)
|
||
|
||
推导价 = 该 (物流, 工艺) 组合下该 (尺码, 颜色) 变体的 SDS 价格,原样透传
|
||
有效价 = 覆盖表命中 ? 覆盖价 : 推导价 // 人工改价入口,见 §5.5
|
||
```
|
||
|
||
- 推导过程:每条成员链接按 `(logisticsLabel, craftLabel)` 归因 → 其变体按
|
||
`(sizeId, colorId)` 落格 → 去重(冲突取最低价)→ 物化为 `priceMatrix`;
|
||
- 展示"起价" = 有效矩阵(推导 ∪ 覆盖)最低格价格;
|
||
- 阶梯折扣表不存在;人工定价仅有覆盖表按格修改一个入口(§5.5)。
|
||
|
||
## 9. 同步联动与重算
|
||
|
||
在 `persistProductDetail`(`apps/api/src/sync/sync.service.ts`)事务提交后:
|
||
|
||
1. 若该 `OriginGood.familyId` 非空,投递**去重的异步重算任务**(进程内队列即可,幂等);
|
||
2. 重算内容:并集尺码表、并集包装规则、价格矩阵、族选项(crafts/logistics 去重列表);
|
||
3. `autoManaged=true`:直接重算覆盖镜像物化字段;
|
||
4. `autoManaged=false`:只置 `stale=true`,绝不静默覆盖人工数据;
|
||
5. 幂等性:重算输入均为确定性数据(成员镜像、覆盖表、自定义成员),同一状态重算结果相同,可安全重试;
|
||
6. 覆盖表(§5.5)与自定义成员数据(§5.6)在重算中**只读**,永不覆盖;
|
||
物化 `priceMatrix` 时把覆盖合并为最终有效矩阵(命中行 `manual: true`)。
|
||
|
||
商品列表同步(`syncProducts`)在 upsert `OriginGood` 时同步刷新四个解析列;
|
||
链接被标记 `delisted` 时,其族在下一次重算中自然剔除该成员的变体与尺码贡献。
|
||
|
||
## 10. API 契约
|
||
|
||
### 10.1 后台(JWT 保护,需按现有权限体系评估,见 §13)
|
||
|
||
| 端点 | 说明 |
|
||
| --- | --- |
|
||
| `GET /product-families?keyword=&page=&pageSize=` | 分页列表(含成员数、stale 标记) |
|
||
| `POST /product-families/auto-group` | 按 3 段规则**预览**可成族分组,`apply=true` 时落库(DG001 18 条一键成族) |
|
||
| `POST /product-families` | 手工建族 |
|
||
| `PATCH /product-families/:id` | 编辑 canonical 字段(名称/主图/国家/分类/详情/主链接/autoManaged) |
|
||
| `POST /product-families/:id/members` | 增删成员 `{ addOriginGoodIds, removeOriginGoodIds }`,变更后触发重算 |
|
||
| `POST /product-families/:id/recompute` | 手动重算 |
|
||
| `GET /product-families/:id/price-overrides` | 覆盖列表(含推导价对照、差异标记) |
|
||
| `PUT /product-families/:id/price-overrides` | 批量 upsert 人工价 `{ items: [{ sizeId, colorId, craft, logistics, price, note }] }`,写后触发重物化 |
|
||
| `DELETE /product-families/:id/price-overrides` | 按格删除覆盖 `{ cells: [...] }`,恢复推导价 |
|
||
| `POST /product-families/:id/members/custom` | 在族内创建自定义成员(人工商品:名称/主图/物流/工艺归因/变体价格/详情) |
|
||
| `GET /origin-goods/tree` | 改为按**真实族**分组展示,替代名称截断的临时分组 |
|
||
|
||
`POST /goods/custom` 保留并扩展:请求体增加解析列(物流/工艺必填)与结构化变体
|
||
(尺码/颜色/价格),创建后可直接指定 `familyId` 挂族;未指定则形成单成员族。
|
||
|
||
### 10.2 公开(无鉴权)
|
||
|
||
**复用原则(D4):不新增任何公开端点、不改变路径与参数语义**。族数据全部以增量字段
|
||
嵌入既有响应,官网现有代码继续工作;详情页消费 `family` 块属于纯增量升级。
|
||
|
||
`GET /public/goods/:goodId`(goodId 仍为 sdsGoodId,主源或副源命中均可,语义不变)详情新增:
|
||
|
||
```jsonc
|
||
{
|
||
"family": {
|
||
"familyCode": "DG001",
|
||
"sizes": [{ "id": "size_1", "name": "S", "available": true }], // 并集
|
||
"colors": [{ "id": "color_1", "name": "黑色", "hex": "#000", "imageUrl": "..." }],
|
||
"crafts": ["单面印花", "双面印花"],
|
||
"logisticsOptions": ["包邮", "专线"],
|
||
"sizeChart": { /* 并集 */ },
|
||
"packageSpecs": { /* 并集 */ },
|
||
"priceMatrix": { /* §5.4 结构(含 manual 标记),嵌入详情而非单独查价接口 */ }
|
||
}
|
||
}
|
||
```
|
||
|
||
- 矩阵直接嵌入详情响应:无阶梯计算,矩阵是静态快照,前端本地联动即可,
|
||
体量与现状(主副源变体拼接)相当甚至更小(去重后);
|
||
- 不可用组合由矩阵行的**缺席**表达(前端禁用对应选项);
|
||
- 读路径切换加配置开关(如 `PUBLIC_DETAIL_FROM_FAMILY`)灰度,可秒回退。
|
||
|
||
## 11. 后台管理前端(apps/admin,D5)
|
||
|
||
### 11.1 现状与入口
|
||
|
||
- 外层壳 `views/product-management/ProductManagementView.vue` 以标签页组织:
|
||
商品配置(`views/goods/GoodsView.vue`,约 2655 行)、数据同步(`views/sync/SyncView.vue`);
|
||
- 本期新增第三个标签页"**产品族**",并改造 GoodsView 的原产品树。
|
||
|
||
### 11.2 新增"产品族"标签页
|
||
|
||
新建 `views/product-family/` 目录,按职责拆组件(避免复刻 GoodsView 的巨型单文件):
|
||
|
||
- `FamilyList.vue`:族列表——关键词/分页/成员数、`stale` 红点、`autoManaged` 状态;
|
||
- `FamilyDetail.vue`:族详情——canonical 字段编辑(名称/主图/国家/分类/主链接/autoManaged)、
|
||
成员管理(SDS 链接增删、创建自定义成员)、手动重算;
|
||
- `AutoGroupDialog.vue`:自动建族——按 3 段规则展示候选分组预览,确认后应用;
|
||
- `PriceOverridePanel.vue`:人工改价矩阵——按 (工艺 × 物流) 切换页签,表格为尺码 × 颜色,
|
||
单元格显示有效价,人工格高亮并展示与推导价的差额,行内编辑即调
|
||
`PUT / DELETE price-overrides`;
|
||
- 图片上传复用现有 `components/ImageUpload.vue`。
|
||
|
||
### 11.3 GoodsView 改造
|
||
|
||
- 右侧原产品树分组依据从**前端名称截断**(`utils/origin-name.ts`,本期后退役)切换为
|
||
`GET /origin-goods/tree` 返回的真实族分组;
|
||
- 原"合并到 Good"交互改为"挂到族":勾选兄弟链接 → 加入既有族或新建族;
|
||
- Good 配置表单增加 `familyId`(选族替代选主源+副源),保留"主链接"概念用于跳转兜底。
|
||
|
||
### 11.4 API client
|
||
|
||
- 新增 `src/api/product-families.ts`(族 CRUD / auto-group / members / overrides / recompute);
|
||
- `src/api/origin-goods.ts`、`src/api/goods.ts` 随契约更新(familyId 字段)。
|
||
|
||
### 11.5 前端测试
|
||
|
||
关键交互(auto-group 预览、覆盖编辑、自定义成员创建、树分组切换)随仓库现有测试设施
|
||
补充组件/交互测试;全量现有测试保持通过。
|
||
|
||
## 12. 迁移与灰度
|
||
|
||
1. **建表加列**:Prisma migration(product_families、OriginGood 四解析列 + familyId、Good.familyId);
|
||
2. **解析回填**:脚本解析全部存量 `OriginGood.goodName` 填四列,输出不可解析清单;
|
||
3. **自动建族**:按 3 段键分组,每组建族(规范名取品名段、familyCode 取 SKU 段)、挂成员;
|
||
**每条非下架链接都归属一个族**,无兄弟则单成员族(统一不变量,免特判);
|
||
4. **合并关系映射**:遍历现有 `good_origin_goods`,每个 Good 的成员集合
|
||
(主源+副源)对应到族(恰好匹配自动族则直接关联;横跨多组则建独立族),回填 `Good.familyId`;
|
||
5. **物化回填**:对全部族跑一次重算,填充并集与价格矩阵;
|
||
6. **读路径灰度**:开关切换 `/public/goods/:goodId` 详情来源;确认无回归后,
|
||
`good_origin_goods` 转只读观察一段时间再废弃;
|
||
7. **自定义商品与覆盖**:存量 `source=CUSTOM` 的 OriginGood 解析列保持为空,由管理员
|
||
按需补填并挂族;覆盖表初始为空,随运营逐步产生,无需数据迁移。
|
||
|
||
分期建议:一期后端(族 + 解析 + 自动建族 + 树接口改造),二期 admin 前端(产品族页 + GoodsView 改造),
|
||
三期(并集物化 + 公开详情灰度切换 + Good.familyId),避免单次变更过大。
|
||
|
||
## 13. 测试要点
|
||
|
||
- 解析器单元测试:golden 样本覆盖真实 552 条中的代表格式 + 畸形样本;
|
||
- 并集合并:同尺码冲突裁决、缺尺码补齐、包装规则合并、warning 记录;
|
||
- 价格矩阵推导:归因正确性、同格子最低价、delisted 成员剔除、幂等重算;
|
||
- 锁定语义:`autoManaged=false` 的族在重算时只置 stale、字段不被覆盖;
|
||
- 覆盖语义:重算后覆盖价保留且生效;覆盖可新增推导矩阵中不存在的格子(校验维度取值);
|
||
删除覆盖恢复推导价;覆盖行在公开矩阵中带 `manual` 标记;
|
||
- 自定义成员:变体/详情不被重算覆盖;物流/工艺归因正确进入矩阵;自动建族不吸收 CUSTOM 成员;
|
||
- 迁移映射:现有 Good 合并关系到族的映射(恰好匹配 / 横跨多组);
|
||
- 公开接口契约:开关两态下的响应形状、任何成员 sdsGoodId 均命中同一族;
|
||
- 全量现有测试保持通过(回归红线)。
|
||
|
||
## 14. 权限评估(按 AGENTS.md 要求)
|
||
|
||
新增后台操作需纳入现有权限体系(User/Role + JWT):
|
||
|
||
- 产品族列表/详情:读权限(对齐现有 origin-goods 读);
|
||
- 建族/自动成族/成员增删/canonical 编辑/手动重算:写权限(对齐现有 goods 配置写);
|
||
- 同步触发重算:系统内部行为,走服务账户,不暴露新端点。
|
||
|
||
上线前需在 `docs/references/authority-matrix-ui.md` 同步权限矩阵。
|
||
|
||
## 15. 默认值与待确认项
|
||
|
||
以下规则已按默认值设计,实施前如需推翻请在此记录:
|
||
|
||
1. 同格子多链接价格冲突 → **取最低价**;
|
||
2. 尺码测量值冲突 → **尺码覆盖最全的成员优先**;
|
||
3. 单链接是否建族 → **是**(统一不变量);
|
||
4. `familyCode` 唯一性 → 全局唯一,允许为空(历史/畸形数据);
|
||
5. 覆盖粒度 → 精确格 `(sizeId, colorId, craft, logistics)`,不做通配符;
|
||
6. 覆盖价方向 → 不限制(可高于或低于推导价),仅校验为正数。
|