Files
inkreach-official-website/plans/feature/public-family-id-feature.md
T
yeuimu 8a052773cd 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、双端构建绿
2026-08-28 18:43:30 +08:00

4.6 KiB
Raw Blame History

公开契约族化: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 增加 printCountPriceMatrix 增加 printCounts: string[]
  • 成员维度来源改为有效标签OriginGoodTag ∋ 物流渠道/印花数量/印刷工艺三组; 人工接管时用人工标签),三组各自取值做笛卡尔(通常 1×1×1);任一组缺失则该成员不进矩阵
  • 弃用 craftLabel 作为维度(保留字段用于展示回退)
  • 同格子多来源取最低价、sources 全保留、人工覆盖并入 —— 规则不变

B. FamilyPriceOverride 加 printCountprisma 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
  • adminvue-tsc + 现有 22 测试
  • 文档:docs/references/product-center.md、structs.md

风险 / 边界

  • 数字族 ID 与数字 sdsGoodId 理论碰撞:族 ID 优先(sdsGoodId 为 SDS 系统 6 位数, 实际不碰撞),Swagger 注明
  • 列表内存分组在商品量级 10⁴ 前可接受,注释留优化方向(物化族表)
  • 旧前端(website)以 sdsGoodId 寻址仍可达(回退路径),后续 website 侧再切换