/** * Backfill all projects into the intake round (and any intermediate rounds * between intake and their earliest assigned round) with COMPLETED state. * * Usage: npx tsx scripts/backfill-intake-round.ts * Add --dry-run to preview without making changes. */ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() const dryRun = process.argv.includes('--dry-run') async function main() { console.log(dryRun ? 'šŸ” DRY RUN — no changes will be made\n' : 'šŸš€ Backfilling intake round states...\n') // Find the intake round const intakeRound = await prisma.round.findFirst({ where: { roundType: 'INTAKE' }, select: { id: true, name: true, sortOrder: true, competitionId: true }, }) if (!intakeRound) { console.log('āŒ No INTAKE round found') return } console.log(`Intake round: "${intakeRound.name}" (sortOrder: ${intakeRound.sortOrder})`) // Get all rounds in the competition ordered by sortOrder const allRounds = await prisma.round.findMany({ where: { competitionId: intakeRound.competitionId }, select: { id: true, name: true, sortOrder: true }, orderBy: { sortOrder: 'asc' }, }) // Find all projects NOT in the intake round const projects = await prisma.project.findMany({ where: { projectRoundStates: { none: { roundId: intakeRound.id }, }, }, select: { id: true, title: true, projectRoundStates: { select: { roundId: true, round: { select: { sortOrder: true } } }, orderBy: { round: { sortOrder: 'asc' } }, }, }, }) console.log(`${projects.length} projects not in intake round\n`) if (projects.length === 0) { console.log('āœ… All projects already in intake round') return } // For each project, create COMPLETED states for intake + any intermediate rounds const toCreate: Array<{ projectId: string; roundId: string; state: 'COMPLETED' }> = [] for (const project of projects) { // Find the earliest round this project is already in const earliestSortOrder = project.projectRoundStates.length > 0 ? Math.min(...project.projectRoundStates.map(ps => ps.round.sortOrder)) : Infinity const existingRoundIds = new Set(project.projectRoundStates.map(ps => ps.roundId)) // Add COMPLETED for intake + all intermediate rounds before the earliest assigned round for (const round of allRounds) { if (round.sortOrder >= earliestSortOrder) break if (existingRoundIds.has(round.id)) continue toCreate.push({ projectId: project.id, roundId: round.id, state: 'COMPLETED', }) } } console.log(`Creating ${toCreate.length} ProjectRoundState records...`) if (!dryRun) { await prisma.projectRoundState.createMany({ data: toCreate, skipDuplicates: true, }) } // Summary by round const byRound = new Map() for (const r of toCreate) { const name = allRounds.find(ar => ar.id === r.roundId)?.name ?? r.roundId byRound.set(name, (byRound.get(name) ?? 0) + 1) } for (const [name, count] of byRound) { console.log(` ${name}: ${count} projects`) } console.log(`\nāœ… Done! ${toCreate.length} records ${dryRun ? 'would be' : ''} created`) } main() .catch((e) => { console.error('āŒ Error:', e) process.exit(1) }) .finally(() => prisma.$disconnect())