/** * 产品族回填脚本(一次性 / 幂等): * 1. 全量 OriginGood 回填四个链接名解析列; * 2. auto-group 全量建族并挂成员(每族建立即重算); * 3. 输出统计与不可解析清单。 * * 运行:pnpm --filter @inkreach/api backfill:product-families * 幂等性:重复执行时步骤 1 数据不变、步骤 2 候选为空(familyId=null 过滤)。 */ import { Prisma } from '@prisma/client'; import { PrismaService } from '../src/prisma/prisma.service'; import { FamilyRecomputeService } from '../src/product-families/family-recompute.service'; import { ProductFamiliesService } from '../src/product-families/product-families.service'; import { parseOriginName } from '../src/product-families/origin-name.parser'; async function main() { const prisma = new PrismaService(); await prisma.onModuleInit(); const recompute = new FamilyRecomputeService(prisma); const families = new ProductFamiliesService(prisma, recompute); // ---- 1. 解析列回填(分批) ---- const BATCH = 100; let parsed = 0; const unparsable: string[] = []; for (;;) { const batch = await prisma.originGood.findMany({ orderBy: { id: 'asc' }, take: BATCH, skip: parsed, select: { id: true, goodName: true }, }); if (batch.length === 0) break; for (const og of batch) { const p = parseOriginName(og.goodName); if (!p.skuCode && !p.craftLabel) unparsable.push(`#${og.id} ${og.goodName ?? ''}`); await prisma.originGood.update({ where: { id: og.id }, data: { skuCode: p.skuCode, logisticsLabel: p.logisticsLabel, craftLabel: p.craftLabel, warehouseLabel: p.warehouseLabel, }, }); } parsed += batch.length; } console.log(`[1/2] parsed ${parsed} origin goods (${unparsable.length} without sku/craft)`); // ---- 2. 自动建族(含逐族重算) ---- const result = await families.autoGroup(true); console.log(`[2/2] created ${result.applied} families`); // ---- 3. 全量族重算兜底(修复建族早于解析列回填等时序造成的空矩阵) ---- const allFamilies = await prisma.productFamily.findMany({ select: { id: true } }); for (const f of allFamilies) { await recompute.recomputeFamily(f.id); } console.log(`[3/3] recomputed ${allFamilies.length} families`); // ---- 统计 ---- const total = await prisma.productFamily.count(); const withMatrix = await prisma.productFamily.count({ where: { priceMatrix: { not: Prisma.DbNull } }, }); const members = await prisma.originGood.count({ where: { familyId: { not: null } } }); const stale = await prisma.productFamily.count({ where: { stale: true } }); console.log(`stats: families=${total}, materialized=${withMatrix}, members=${members}, stale=${stale}`); if (unparsable.length) { console.log('unparsable names:'); for (const line of unparsable) console.log(` - ${line}`); } await prisma.$disconnect(); } main().catch((err) => { console.error(err); process.exit(1); });