From dd6a540bf302eebc7a71e894983941d9641ae829 Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Thu, 3 Sep 2026 02:53:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(product-families):=20=E4=B8=8D=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E9=93=BE=E6=8E=A5=E7=A7=BB=E5=87=BA=E6=97=8F=EF=BC=88?= =?UTF-8?q?=E7=BA=AF=E6=95=B0=E6=8D=AE=E4=BF=AE=E5=A4=8D=EF=BC=89=E2=80=94?= =?UTF-8?q?=E2=80=94=E7=9F=A9=E9=98=B5=E4=B8=8D=E5=86=8D=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E4=B8=8D=E6=89=93=E5=8D=B0=E5=B7=A5=E8=89=BA=EF=BC=8CBRTF004?= =?UTF-8?q?=20=E6=81=A2=E5=A4=8D=E7=83=AB=E7=94=BB=C3=97=E5=8D=95=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 生产数据操作(未改源代码):11 条光板/不打印链接 family_id 置空(族 1/2/63/135/148), 名下 3 商品脱族,族 148 主链接 964→965、族 135 主链接置空 - 容器内直调已部署 FamilyRecomputeService 重算 5 族:全库矩阵不再含「不打印」, 族 148 = 烫画×单面印花×不包邮(min 19),public 端口验证通过 - 回滚存档:deploy/backups/20260903-noprint-removal/(全库 dump + 受影响行 CSV + RESTORE.md) - 文档:product-center/structs 记录数据约定与回流风险(organize/autoGroup/人工改标签会回挂散链接); AGENTS.md 沉淀 占位借词表值/物化JSON需显式重算/数据修复三件套 三条经验 --- AGENTS.md | 3 ++ docs/references/product-center.md | 21 ++++++--- docs/references/structs.md | 2 +- plans/fix/no-print-links-removal-fix.md | 60 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 plans/fix/no-print-links-removal-fix.md diff --git a/AGENTS.md b/AGENTS.md index f8d8181..3004acf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,3 +113,6 @@ Prisma Postgres 最佳实践: prisma-postgres 技能 - **改"镜像字段"的数据前先查覆盖路径**:分类名每小时被 `syncCategories` 用 SDS 原名无条件覆盖——直接改库/界面改名都会被下一轮同步冲回。此类字段的清洗必须写进同步入库路径(如 `cleanCategoryDisplayName`),存量靠回填脚本;数据先改而镜像未重部署的窗口期内会被冲回一次,重部署后自动恢复。 - **合并分叉血统时警惕"文本无冲突、行为有冲突"**:git 自动合并可能把两侧对同一机制的不同设计(如 热转印=烫画别名 vs 一等公民词表)拼进一个文件,编译通过但行为矛盾——合并后必须跑两侧血统各自的测试才能暴露;行级冲突反而不是最危险的。另:`git status | head` 截断会漏看冲突文件,连续 cherry-pick 后要全仓 `grep '<<<<<<<'` 扫一遍。 - **生产栈收敛三件套**:动栈结构前先 `pg_dump -Fc` 双库 + 数据卷归档 + 配置快照落 `deploy/backups/<时间戳>/` 并写 RESTORE.md;切换用"外部卷 external 引用 + down 不带 -v + 切换前后行数 diff"保证数据零丢失;先 `docker compose build` 预构建再 down/up,把停机窗口压到秒级。 +- **矩阵维度占位不得借用业务词表既有值**:曾让「不打印」链接的印花数量占位为「单面印花」,导致顾客端出现「不打印+单面印花」的自相矛盾组合(不打印根本无印花面)。2026-09-03 数据收口:光板/不打印链接不入族(`family_id=NULL`),矩阵不计算不打印工艺。**回流风险**:`organize`/`autoGroup` 会把散链接按分类重新归族、人工改链接标签会触发 `attachToMatchingFamily` 回挂——跑这两类操作前必须先排查光板链接(清单与回滚见 `deploy/backups/20260903-noprint-removal/`)。 +- **重部署不会自愈物化 JSON**:`price_matrix` 是物化列,代码修好/词表修正后已物化的矩阵**不会自动重算**(重算只在归族/成员变化/详情同步/标签调整等事件时触发)——族 148 曾因旧容器物化的坏矩阵跨镜像存活,"代码明明对了线上却没变"。此类修复收尾必须显式触发受影响族重算(可容器内 `node -e` 直调 `FamilyRecomputeService.recomputeFamily`,用已部署代码、免重建免鉴权)。 +- **批量数据修复前"全量 dump + 受影响行快照 + RESTORE.md"三件是底线**:全量 `pg_dump -Fc` 兜灾难恢复,受影响行 CSV(含改前外键值)支持精准回滚,RESTORE.md 写清逐表恢复命令与重算步骤;事务内逐步核对影响行数与预期一致再 COMMIT。 diff --git a/docs/references/product-center.md b/docs/references/product-center.md index c2c6e18..2f3934d 100644 --- a/docs/references/product-center.md +++ b/docs/references/product-center.md @@ -57,7 +57,8 @@ **模型**:SDS 叶子分类即产品模型(如分类 `DG001 180G纯棉T恤(JSA002)` 下 16 条链接), 族 = 同分类链接的合并体;链接名解析列(`国家(物流)品名-SKU-工艺[-仓库]`)提供 -物流/工艺归因。例:DG001 族 = 16 链接 × 3 工艺 × 3 物流 × 9 尺码(S~XXXXXL 并集)× 20 颜色 ≈ 470 格价格矩阵,起价取光板(不打印)最低价。 +物流/工艺归因。例:DG001 族 = 16 链接 × 3 工艺 × 3 物流 × 9 尺码(S~XXXXXL 并集)× 20 颜色 ≈ 470 格价格矩阵,起价取矩阵内最低价。 +(2026-09-03 起:**光板/不打印链接不配置进族**,矩阵不再计算「不打印」工艺,起价只来自印花工艺链接。) **价格**:`价格 = f(尺码, 颜色, 物流, 工艺[含印花数量])`,全部从成员链接变体推导透传 (同格子多仓库取最低价,来源全保留);`family_price_overrides` 表支持按格人工改价, @@ -112,7 +113,12 @@ curl -X POST /product-families/organize -H "$AUTH" # 后台「整理 未整理(无标签)的 SDS 成员不进矩阵,整理后齐全。 派生默认(脚本层假设,显式可审查):工艺无关键字 → 烫画;印花数量无单/双面且工艺非不打印 → -单面印花;不打印/光板 无印花面不补(矩阵中印花数量以单面占位,纯结构化规则)。 +单面印花;不打印/光板 无印花面不补。 +**数据约定(2026-09-03 起)**:不打印/光板链接不进族(`family_id=NULL`),SKU 矩阵不计算 +「不打印」工艺——不存在「不打印×单面印花」这类矛盾组合。⚠️ 回流风险:运行 `organize` / +`POST /product-families/autoGroup` 会把这些散链接按分类重新归族;人工编辑其标签也会触发 +`attachToMatchingFamily` 回挂。重新归族前须先排查光板链接(备份与清单见 +`deploy/backups/20260903-noprint-removal/`)。 「恢复自动」(链接标签重置)本身是显式人工动作,同样走整理服务的单链接派生。 **人工标签缺啥补啥 + 旧词别名**(2026-09-02 起): @@ -121,8 +127,9 @@ curl -X POST /product-families/organize -H "$AUTH" # 后台「整理 就按链接名称自动补哪组(并入人工集合一起存为 manual 行,人工勾选值永不被动),响应带 `filledDimensionTags`;未归族链接随后自动并入匹配族(同 SDS 分类优先,回退族语义名称键), 响应带 `attachedFamilyId`。 -- 工艺旧词「热转印」按别名归一为「烫画」进价格矩阵(`CRAFT_TAG_ALIASES`),标签字典本身不动。 - 历史背景:曾有 16 条人工链接因勾了旧词「热转印」被封闭词表静默踢出合并矩阵。 +- 工艺「热转印」是矩阵词表一等公民(`DIM_VALUES.craft` 含 烫画/直喷/不打印/热转印), + 打了该标签的链接按「热转印」工艺进价格矩阵;标签字典本身不动。 + 历史背景:曾有 16 条人工链接因勾了「热转印」被封闭词表静默踢出合并矩阵。 - 名称回退分组键为**族语义键** `familyNameKey`(第 1 段剥离物流备注、保留 国家+品名+SKU): 包邮/不包邮、单面/双面印花是矩阵维度,不再拆族;不同国家/不同款号仍不同族。 @@ -146,7 +153,7 @@ curl -X POST /product-families/organize -H "$AUTH" # 后台「整理 "family": { "familyId": "12", "familyCode": "DG015", "familyName": "DG015 180G纯棉T恤", "sizes": [...], "colors": [...], - "printCounts": ["单面印花", "双面印花"], "crafts": ["烫画", "直喷", "不打印"], "logistics": ["包邮", "不包邮"], + "printCounts": ["单面印花", "双面印花"], "crafts": ["烫画", "直喷"], "logistics": ["包邮", "不包邮"], "sizeChart": { /* 并集 */ }, "packageSpecs": { /* 并集 */ }, "priceMatrix": { /* rows 为 {sizeId, colorId, printCount, craft, logistics, price, manual, sources} */ }, "minPrice": "29.5" @@ -156,8 +163,8 @@ curl -X POST /product-families/organize -H "$AUTH" # 后台「整理 矩阵维度来源是**链接级标签**(印花数量/印刷工艺/物流渠道三组;人工接管后按人工标签), 不再用原始链接名的第 3 段(`craftLabel` 含「直喷单面/光板不打印」等噪声)。名称派生规则已 -覆盖裸「单面/双面」写法(如「直喷双面」→ 双面印花 + 直喷)。光板/不打印链接无印花面概念, -矩阵中印花数量维回退「单面印花」占位。前端本地按五维联动 `priceMatrix` 即可实时算价 +覆盖裸「单面/双面」写法(如「直喷双面」→ 双面印花 + 直喷)。光板/不打印链接不进族、 +无印花面概念,不参与矩阵维度。前端本地按五维联动 `priceMatrix` 即可实时算价 (设计 D4)。 **边界声明**:价格区间筛选(minPrice/maxPrice)仍作用于链接 `goodPrice`(族内任一链接命中即 diff --git a/docs/references/structs.md b/docs/references/structs.md index 0aecb10..043d57e 100644 --- a/docs/references/structs.md +++ b/docs/references/structs.md @@ -73,7 +73,7 @@ apps/api/ │ ├── tag-groups/ # 标签分组 CRUD(受 JWT 保护,含批量排序) │ ├── positions/ # 坑位 CRUD(受 JWT 保护) │ ├── origin-goods/ # SDS 原始商品快照(只读分页 + 配置状态树 + 链接级标签人工接管) -│ ├── product-families/ # 产品族(SPU 层):CRUD / auto-group(并入已有族优先,familyNameKey 族语义键)/ attachToMatchingFamily(单链接自动归族)/ consolidateFragments(碎片族合并)/ 成员管理 / 自定义成员 / 价格覆盖 / 重算 / 按链接名称派生标签(auto-tag-rules,含 热转印→烫画 别名) +│ ├── product-families/ # 产品族(SPU 层):CRUD / auto-group(并入已有族优先,familyNameKey 族语义键)/ attachToMatchingFamily(单链接自动归族)/ consolidateFragments(碎片族合并)/ 成员管理 / 自定义成员 / 价格覆盖 / 重算 / 按链接名称派生标签(auto-tag-rules,含 热转印→烫画 别名)。数据约定(2026-09-03 起):光板/不打印链接不入族、矩阵不计算不打印工艺;auto-group/organize/人工改标签会把散链接回挂,运行前须排查(备份见 deploy/backups/20260903-noprint-removal/) │ ├── goods/ # 商品 CRUD + 批量优先级 + 批量创建 + 展示名规范化(品名 SKU,剥 ASCII 型号限定词,纯款号按 SDS 分类名补描述) │ ├── sync/ # SDS 同步:分类 / 商品 / 同步日志 │ ├── public/ # 公开 API:分类树 / 国家 / 商品分页 / 商品详情 diff --git a/plans/fix/no-print-links-removal-fix.md b/plans/fix/no-print-links-removal-fix.md new file mode 100644 index 0000000..c025dac --- /dev/null +++ b/plans/fix/no-print-links-removal-fix.md @@ -0,0 +1,60 @@ +# No-Print Links Removal Fix Implementation Plan(纯数据修复,不改源代码) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 「不打印/光板」链接移出商品族,SKU 矩阵不再出现「不打印」工艺(含其「单面印花」占位组合);BRTF004(族 148)恢复烫画×单面印花矩阵。**用户明确:本修复不修改源代码,纯数据操作。** + +**Background / Root cause(2026-09-03 诊断):** + +1. 「不打印×单面印花」来自 `family-recompute.service.ts` 的结构化占位规则(不打印链接无印花数量标签 → 占位单面印花)。占位逻辑在代码里,**但不需要改代码**:把不打印链接移出族后,占位分支对无成员的族不再生效,矩阵自然无该组合。 +2. 族 148 烫画消失根因:**陈旧物化矩阵**。族 148 两成员(964 光板不打印、965 烫画单面)均在族内、未下架、标签齐全;矩阵物化于 09-02 16:00(旧容器时期),09-03 02:05 新镜像部署后无任何事件触发重算。用**当前已部署的正确代码**重算即恢复烫画。 +3. 全库审计(141 个有活跃烫画成员的族):除族 148 外全部健康(crafts 含烫画、printCounts 为单面或单面+双面)。无 craft=不打印 覆盖价,无 autoManaged=false 冻结族,无未归族烫画链接。 + +**最终数据修复面:** + +| 族 | 移出的不打印成员链接 | 主链接修正 | 名下商品脱族 | +| --- | --- | --- | --- | +| 1 (DG001) | 166, 731, 859, 961 | 无需(primary=163 不变) | good 361 | +| 2 (DG004) | 171, 749, 763, 852 | 无需(primary=167 不变) | — | +| 63 (METP001) | 759 | 无需(primary=339 不变) | — | +| 135 (PLJM030) | 760 | primary→NULL(移除后无剩余成员,族变空矩阵,公列表自然消失) | good 554 | +| 148 (BRTF004) | 964 | primary→965(烫画链接承接) | good 359(good 360 承接公开代表行) | + +**回流风险(不改代码的代价,需文档化 + 运行纪律):** +- `organize` / `POST /product-families/autoGroup`:会把 family_id=null 的链接按分类重新归族 → **不打印链接会回流**。运行前须先排查,或将来补代码守卫。 +- 人工编辑链接标签(`origin-goods.service.ts:184`)会触发 `attachToMatchingFamily` 把散链接挂回匹配族。 +- 同步钩子无回流风险(`sync.service.ts:690` 只对 familyId 非空的链接重算)。 + +--- + +### Task 1: 备份(用户要求的风险回滚存档) + +- [ ] `deploy/backups/20260903-noprint-removal/`:`pg_dump -Fc` 全库 + 受影响行 CSV(origin_goods 11 行改前 family_id、goods 3 行、product_families 5 行含 price_matrix 全量)+ `RESTORE.md` 逐表恢复命令;`pg_restore --list` 验证备份完整 + +### Task 2: 数据修复(单事务) + +- [ ] `UPDATE origin_goods SET family_id=NULL` 11 条链接 +- [ ] `UPDATE goods SET family_id=NULL`(origin_good_id ∈ 859/760/964 且 family_id 非空) +- [ ] 族 148 `primary_origin_good_id=965`;族 135 `primary_origin_good_id=NULL` +- [ ] 提交前逐条核对影响行数 = 预期(11 / 3 / 1 / 1) + +### Task 3: 容器内直调重算(用已部署代码,不重建镜像) + +- [ ] `docker exec deploy-v2-api-1 node -e` 实例化 `FamilyRecomputeService(new PrismaClient())`,对族 1/2/63/135/148 依次 `recomputeFamily` + +### Task 4: 验证 + +- [ ] SQL 终检:全库无 price_matrix 含「不打印」;族 148 矩阵 crafts=['烫画']、printCounts=['单面印花']、minPrice=烫画链接价(19) +- [ ] `GET /public/goods?keyword=BRTF004`:一条代表行(good 360),价格 19 +- [ ] `GET /public/goods/148`:family 块 crafts 含烫画、printCounts=['单面印花']、无不打印 +- [ ] `GET /public/goods?keyword=PLJM030`:无结果(仅光板的族已隐去) + +### Task 5: 文档与沉淀 + +- [ ] `docs/references/product-center.md`:不打印链接不参与族矩阵的数据约定 + 回流风险 +- [ ] `docs/references/structs.md` 同步 +- [ ] AGENTS.md 沉淀:①矩阵维度占位借用词表既有值导致语义硬伤(本次以数据收口:不打印链接不入族)②代码修复/重部署后物化 JSON 不会自愈——须显式触发全量重算(族 148 教训)③批量数据修复前 pg_dump+受影响行导出+RESTORE.md 三件套 + +### Task 6: 提交合并 + +- [ ] 本文档 + 文档更新提交 `bugfix/no-print-links-removal-yeuimu`,合并回 develop