/** * 按“主图 + SKU 图 + 详情图齐全”规则回填任务-详情的 isFullSet。 * * 默认只预览结果,不写库。 * 真正写入请加:--apply * 可选限制数量:--limit=200 */ import { PrismaClient } from '@prisma/client'; import { isTaskDetailFullSetFromAssets } from '../lib/task-detail-full-set'; const prisma = new PrismaClient(); function parseLimitArg(): number | undefined { const limitArg = process.argv.find((arg) => arg.startsWith('--limit=')); if (!limitArg) { return undefined; } const parsed = Number(limitArg.split('=')[1]); if (!Number.isFinite(parsed) || parsed <= 0) { throw new Error(`无效的 --limit 参数:${limitArg}`); } return Math.floor(parsed); } async function main() { const shouldApply = process.argv.includes('--apply'); const limit = parseLimitArg(); console.log('='.repeat(72)); console.log('任务-详情 isFullSet 回填脚本'); console.log(`模式: ${shouldApply ? '写入数据库' : '仅预览(dry-run)'}`); if (limit) { console.log(`限制数量: ${limit}`); } console.log('规则: 同时拥有主图、SKU 图、详情图 => 整套'); console.log('='.repeat(72)); const materials = await prisma.material.findMany({ where: { materialType: '任务-详情', deletedAt: null, }, select: { id: true, taskNo: true, productModel: true, isFullSet: true, createdAt: true, assets: { select: { assetType: true, }, }, }, orderBy: { createdAt: 'desc', }, ...(limit ? { take: limit } : {}), }); let shouldBeTrue = 0; let shouldBeFalse = 0; let toTrue = 0; let toFalse = 0; let unchanged = 0; const changedMaterials: Array<{ id: string; taskNo: string | null; productModel: string; before: boolean; after: boolean; assetTypes: string[]; }> = []; for (const material of materials) { const derivedIsFullSet = isTaskDetailFullSetFromAssets(material.assets); const currentIsFullSet = Boolean(material.isFullSet); if (derivedIsFullSet) { shouldBeTrue += 1; } else { shouldBeFalse += 1; } if (derivedIsFullSet === currentIsFullSet) { unchanged += 1; continue; } if (derivedIsFullSet) { toTrue += 1; } else { toFalse += 1; } changedMaterials.push({ id: material.id, taskNo: material.taskNo, productModel: material.productModel, before: currentIsFullSet, after: derivedIsFullSet, assetTypes: material.assets.map((asset) => asset.assetType), }); } console.log(`总素材数: ${materials.length}`); console.log(`应为整套: ${shouldBeTrue}`); console.log(`应为非整套: ${shouldBeFalse}`); console.log(`待改为 true: ${toTrue}`); console.log(`待改为 false: ${toFalse}`); console.log(`无需修改: ${unchanged}`); if (changedMaterials.length > 0) { console.log('\n待修改样例(最多展示前 20 条):'); changedMaterials.slice(0, 20).forEach((material, index) => { console.log( `${index + 1}. ${material.productModel} | taskNo=${material.taskNo || '无'} | ${material.before} -> ${material.after} | assets=${material.assetTypes.join(', ')}`, ); }); } else { console.log('\n没有需要修改的素材。'); } if (!shouldApply) { console.log('\n当前为 dry-run,未写入数据库。'); console.log('如需真正回填,请执行: npx tsx scripts/backfill-task-detail-full-set.ts --apply'); return; } if (changedMaterials.length === 0) { console.log('\n没有可写入的变更,结束。'); return; } console.log('\n开始写入数据库...'); for (const material of changedMaterials) { await prisma.material.update({ where: { id: material.id }, data: { isFullSet: material.after, }, }); } console.log(`写入完成,共更新 ${changedMaterials.length} 条素材。`); } main() .catch((error) => { console.error('脚本执行失败:', error); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });