# 公开契约族化: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 侧再切换