Files
MOPC-Portal/scripts/backfill-intake-round.ts

113 lines
3.3 KiB
TypeScript
Raw Normal View History

/**
* 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<string, number>()
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())