feat(public): family-first contract — goodId=familyId, strict 5-dim price matrix
公开契约族化(前端只需知道款号/族): - GET /public/goods 一族一条(goodId=族ID,price=族起价,分页作用于分组后); 无族商品(自定义)不进任何公开端点(列表/首页/分类树/标签统计) - GET /public/goods/:goodId 仅认族 ID;公共字段取代表 Good,变体=全体成员并集, 尺码表/包装规格=族物化并集;旧 SDS 链接 ID 寻址 404 - priceMatrix 严格五维:尺码×颜色×印花数量×工艺×物流;维度来源改为链接级 标签(人工接管按人工标签),弃用原始 craftLabel;CUSTOM 成员尊重显式标签 - 名称派生补裸「单面/双面」写法(直喷双面→双面印花+直喷,18 条存量链接修复) - family_price_overrides 加 print_count 列(五键唯一),PUT/DELETE/校验五键化 - admin 编辑弹窗矩阵消费适配(SKU 列直读 printCount,成员格子三维匹配, 改价 payload 带 printCount) - 存量 339 族已全量重算;api 162/162、admin 22/22、双端构建绿
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# 公开契约族化:goodId = 族ID + 五维价格矩阵
|
||||
|
||||
## 背景 / 动机
|
||||
|
||||
- 现状 `GET /public/goods/{goodId}` 以 SDS 链接 ID 寻址,`findFirst` 任选一条 Good;
|
||||
一个族在库内常配置多条 Good(92 个已配置族中 78 个是多条,如加拿大 CATM001 一族 4 条),
|
||||
列表因此出现同款重复,前端被迫理解"族背后有多少商品"。
|
||||
- 价格矩阵把 印花数量+工艺 混在一个 `craft` 维(原始链接名第 3 段,含
|
||||
`直喷单面`/`光板不打印`/解析噪声,且 47 个成员为 null),不是严格的五维。
|
||||
|
||||
目标契约(用户拍板):**前端只知道款号(族)**——
|
||||
|
||||
- `goodId` = 族 ID;前端不知道族背后有几条 Good / 几个链接
|
||||
- 详情 = 款的公共信息(名称、主图、国家、分类、商品详情文案、尺码表、包装规格)
|
||||
+ 价格矩阵
|
||||
- 价格严格五维:**尺码 × 颜色 × 物流 × 工艺 × 印花数量 → 价格**
|
||||
|
||||
## 事实核查(2026-08-28)
|
||||
|
||||
- 0 个族跨国家/跨分类 → 国家、分类可作为族的公共属性
|
||||
- `family_price_overrides` 0 行 → 加维度无数据回填负担
|
||||
- 无族 Good 6 条(source=CUSTOM)→ 必须保留 sdsGoodId 寻址回退
|
||||
- 链接级标签已全覆盖(536/536),三组派生标签是矩阵维度的可靠来源
|
||||
|
||||
## 方案
|
||||
|
||||
### A. priceMatrix 五维化(family-recompute.service.ts)
|
||||
|
||||
- `PriceMatrixRow` 增加 `printCount`;`PriceMatrix` 增加 `printCounts: string[]`
|
||||
- 成员维度来源改为**有效标签**(OriginGoodTag ∋ 物流渠道/印花数量/印刷工艺三组;
|
||||
人工接管时用人工标签),三组各自取值做笛卡尔(通常 1×1×1);任一组缺失则该成员不进矩阵
|
||||
- 弃用 `craftLabel` 作为维度(保留字段用于展示回退)
|
||||
- 同格子多来源取最低价、sources 全保留、人工覆盖并入 —— 规则不变
|
||||
|
||||
### B. FamilyPriceOverride 加 printCount(prisma migration)
|
||||
|
||||
- 新列 `print_count`(默认 `单面印花`,迁移时 0 行数据)
|
||||
- 唯一键扩展为 (familyId, sizeId, colorId, printCount, craft, logistics)
|
||||
|
||||
### C. product-families.service 覆盖价三处五键化
|
||||
|
||||
- `PriceOverrideItemDto` + `putPriceOverrides` 校验(allowed.printCounts)
|
||||
- `listPriceOverrides` 行匹配五键
|
||||
|
||||
### D. public 契约族化(public.service.ts)
|
||||
|
||||
1. `getGoods`:过滤条件不变(作用于 Good),**取全量匹配后内存按族分组**
|
||||
(规模 241 条 Good,注释说明;无族 Good 各自成组)。
|
||||
- 组代表 = 现有排序的第一条(priority desc → createdAt desc)
|
||||
- `goodId`:有族 → `familyId`;无族 → `sdsGoodId`(自定义商品)
|
||||
- `price`:族 minPrice(批量查 priceMatrix),回退 og price
|
||||
- `total` = 分组数,分页在分组后
|
||||
2. `getGood(id)`:**纯数字先按族 ID 解析**(族 + 其 Good + 成员变体并集),
|
||||
未命中再回退 sdsGoodId 旧路径(自定义商品/向后兼容)。
|
||||
族详情:公共字段取代表 Good;detail 文案取主链接;尺码表/包装规格用族并集;
|
||||
media/mediaByColor/variants 全成员并集;`family` 块 = 五维矩阵 + minPrice。
|
||||
族下无在售 Good → 404。`PUBLIC_DETAIL_FROM_FAMILY=false` 应急开关行为不变。
|
||||
3. `getHomeGoods`:按族去重(排序不变,同族保留最前一条)。
|
||||
|
||||
### E. admin 适配(GoodsEditDialog.vue)
|
||||
|
||||
- SKU 列:印花数量列直接读 `row.printCount`(不再从 craft 字符串猜测)
|
||||
- `loadMemberPrices`:按 printCount+craft+logistics 三键匹配该成员格子;
|
||||
维度优先取成员 `originGoodTags`(人工接管后仍准确),回退 `linkDims(goodName)`
|
||||
- 保存覆盖价 payload 带 printCount
|
||||
|
||||
### F. 存量族全量重算
|
||||
|
||||
一次性脚本:对所有 ProductFamily 逐个 `recomputeFamily`(矩阵物化 JSON 结构变更)。
|
||||
|
||||
### G. 测试与文档
|
||||
|
||||
- family-recompute spec:五维矩阵(含 人工标签 覆盖派生、缺组跳过、跨成员最低价)
|
||||
- product-families spec:覆盖价五键校验
|
||||
- public spec:列表按族去重(同族 N 条 → 1 条,goodId=familyId)、getGood(族ID)、
|
||||
自定义商品回退、home-goods 去重
|
||||
- public-family-block spec:矩阵 printCounts
|
||||
- admin:vue-tsc + 现有 22 测试
|
||||
- 文档:docs/references/product-center.md、structs.md
|
||||
|
||||
## 风险 / 边界
|
||||
|
||||
- 数字族 ID 与数字 sdsGoodId 理论碰撞:族 ID 优先(sdsGoodId 为 SDS 系统 6 位数,
|
||||
实际不碰撞),Swagger 注明
|
||||
- 列表内存分组在商品量级 10⁴ 前可接受,注释留优化方向(物化族表)
|
||||
- 旧前端(website)以 sdsGoodId 寻址仍可达(回退路径),后续 website 侧再切换
|
||||
Reference in New Issue
Block a user