feat(api): good family_id column with backfill
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "goods" ADD COLUMN "family_id" BIGINT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "goods_family_id_idx" ON "goods"("family_id");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "goods" ADD CONSTRAINT "goods_family_id_fkey" FOREIGN KEY ("family_id") REFERENCES "product_families"("family_id") ON DELETE SET NULL ON UPDATE NO ACTION;
|
||||||
|
|
||||||
|
-- Backfill: Good 的族 = 其主链接所属族(派生数据,后续由服务层联动维护)
|
||||||
|
UPDATE "goods" SET "family_id" = (
|
||||||
|
SELECT "o"."family_id" FROM "origin_goods" "o"
|
||||||
|
WHERE "o"."origin_good_id" = "goods"."origin_good_id"
|
||||||
|
);
|
||||||
@@ -212,6 +212,7 @@ model Position {
|
|||||||
model Good {
|
model Good {
|
||||||
id BigInt @id @default(autoincrement()) @map("good_id")
|
id BigInt @id @default(autoincrement()) @map("good_id")
|
||||||
originGoodId BigInt @map("origin_good_id")
|
originGoodId BigInt @map("origin_good_id")
|
||||||
|
familyId BigInt? @map("family_id")
|
||||||
countryId BigInt @map("country_id")
|
countryId BigInt @map("country_id")
|
||||||
categoryId BigInt @map("category_id")
|
categoryId BigInt @map("category_id")
|
||||||
tagId BigInt? @map("tag_id")
|
tagId BigInt? @map("tag_id")
|
||||||
@@ -223,6 +224,7 @@ model Good {
|
|||||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
originGood OriginGood @relation(fields: [originGoodId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
originGood OriginGood @relation(fields: [originGoodId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
||||||
|
family ProductFamily? @relation(fields: [familyId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
||||||
country Country @relation(fields: [countryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
country Country @relation(fields: [countryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
||||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
||||||
tag Tag? @relation(fields: [tagId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
tag Tag? @relation(fields: [tagId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
||||||
@@ -231,6 +233,7 @@ model Good {
|
|||||||
mergedOriginGoods GoodOriginGood[]
|
mergedOriginGoods GoodOriginGood[]
|
||||||
|
|
||||||
@@index([originGoodId])
|
@@index([originGoodId])
|
||||||
|
@@index([familyId])
|
||||||
@@index([countryId])
|
@@index([countryId])
|
||||||
@@index([categoryId])
|
@@index([categoryId])
|
||||||
@@index([tagId])
|
@@index([tagId])
|
||||||
@@ -292,6 +295,7 @@ model ProductFamily {
|
|||||||
|
|
||||||
originGoods OriginGood[]
|
originGoods OriginGood[]
|
||||||
priceOverrides FamilyPriceOverride[]
|
priceOverrides FamilyPriceOverride[]
|
||||||
|
goods Good[]
|
||||||
country Country? @relation(fields: [countryId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
country Country? @relation(fields: [countryId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
||||||
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull, onUpdate: NoAction)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# 产品族三期(公开读路径灰度 + Good.familyId)实施计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** `Good.familyId` 落库并在公开详情中灰度暴露族数据(并集尺码表/包装/五维价格矩阵),零新增公开端点、开关可秒回退(设计 D4)。
|
||||||
|
|
||||||
|
**Architecture:** `Good.familyId` 是**派生数据**:创建时自动取主链接的族;族成员变更时联动刷新。公开详情的族块直接来自 `ProductFamily` 物化 JSON(零额外查询),由环境变量 `PUBLIC_DETAIL_FROM_FAMILY`(默认 false)控制是否输出。
|
||||||
|
|
||||||
|
**明确的范围边界:** 列表/筛选/排序仍基于主源 `goodPrice`(SQL 层无法廉价解析族矩阵 JSON),仅详情页价格以矩阵为准;`good_origin_goods` 转只读保留,观察期后再删(另行任务)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Schema + 迁移 + 回填
|
||||||
|
|
||||||
|
- [ ] `Good` model 增加 `familyId BigInt? @map("family_id")` + `family ProductFamily? @relation(... SetNull)` + `@@index([familyId])`;`ProductFamily` relations 加 `goods Good[]`。
|
||||||
|
- [ ] `npx prisma migrate dev --name add_good_family_id`,迁移 SQL 末尾追加回填:
|
||||||
|
`UPDATE goods SET family_id = (SELECT family_id FROM origin_goods WHERE origin_goods.origin_good_id = goods.origin_good_id);`
|
||||||
|
- [ ] Commit `feat(api): good family_id column with backfill`
|
||||||
|
|
||||||
|
### Task 2: Good.familyId 派生逻辑
|
||||||
|
|
||||||
|
- [ ] `goods.service.ts`:`create` / `batchCreate` / `createCustom` 创建 Good 时 `familyId = originGood.familyId`(含 custom 直挂的 familyId);
|
||||||
|
- [ ] `product-families.service.ts`:`updateMembers` / `tryAutoAttachToFamily`(sync.service)成员移动后联动
|
||||||
|
`UPDATE goods SET family_id`(`good.updateMany({ where: { originGoodId: { in: movedIds } }, data: { familyId } })`,摘除时置 null)。
|
||||||
|
- [ ] 测试:创建 Good 带 familyId、成员移动联动(集成)。
|
||||||
|
- [ ] Commit `feat(api): derive good family membership`
|
||||||
|
|
||||||
|
### Task 3: 公开详情族块(灰度开关)
|
||||||
|
|
||||||
|
- [ ] `PUBLIC_GOOD_INCLUDE` 的 `originGood.include` 加 `family: { select: { id, familyCode, familyName, sizeChart, packageSpecs, priceMatrix } }`;
|
||||||
|
- [ ] `PublicGoodDetailDto` 增加可选 `family` 字段;`toPublicGoodDetail` 在
|
||||||
|
`process.env.PUBLIC_DETAIL_FROM_FAMILY === 'true'` 且 `originGood.family` 存在时输出
|
||||||
|
`{ familyId, familyCode, familyName, sizes, colors, crafts, logistics, sizeChart, packageSpecs, priceMatrix, minPrice }`
|
||||||
|
(minPrice = 矩阵 rows 最低价);开关关闭时**完全不含**该字段(响应形状与现状逐字节一致)。
|
||||||
|
- [ ] 测试:开关两态(spec 内直接改 env 再实例化 service)、族块字段正确、无族 Good 字段缺失。
|
||||||
|
- [ ] Commit `feat(api): public good detail family block behind flag`
|
||||||
|
|
||||||
|
### Task 4: 验证 + 文档 + 合并
|
||||||
|
|
||||||
|
- [ ] api 全量测试两轮全绿;`pnpm -r build` 通过。
|
||||||
|
- [ ] 冒烟:开关 off 响应无 family 键;on 时含 DG001 族块(470 格)。
|
||||||
|
- [ ] 更新 `docs/references/structs.md`(Good.familyId、公开详情族块、环境变量)、`docs/references/product-center.md`(三期行为与边界声明)、`README.md` 环境变量说明。
|
||||||
|
- [ ] 遵循 verification-before-completion;合并回 develop。
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
- 设计 §10.2 复用原则 ✓(零新增端点);§5.3 Good.familyId ✓;§12.6 读路径灰度 ✓(开关粒度为详情族块);
|
||||||
|
- 列表价/筛选不动已作为边界显式声明(SQL 不可行 + 避免展示价与筛选价不一致);
|
||||||
|
- `good_origin_goods` 保留只读,删除另立任务(观察期要求)。
|
||||||
Reference in New Issue
Block a user