diff --git a/prisma/migrations/20260304000000_add_round_finalization_fields/migration.sql b/prisma/migrations/20260304000000_add_round_finalization_fields/migration.sql new file mode 100644 index 0000000..74c2c5a --- /dev/null +++ b/prisma/migrations/20260304000000_add_round_finalization_fields/migration.sql @@ -0,0 +1,79 @@ +-- Round finalization fields +ALTER TABLE "Round" ADD COLUMN "gracePeriodEndsAt" TIMESTAMP(3); +ALTER TABLE "Round" ADD COLUMN "finalizedAt" TIMESTAMP(3); +ALTER TABLE "Round" ADD COLUMN "finalizedBy" TEXT; + +-- ProjectRoundState proposed outcome for finalization pool +ALTER TABLE "ProjectRoundState" ADD COLUMN "proposedOutcome" "ProjectRoundStateValue"; + +-- Mark already-closed rounds as pre-finalized IF their projects were already +-- advanced to the IMMEDIATELY NEXT round (sortOrder = current + 1). +-- We check the next sequential round only, not any subsequent round, because +-- projects can appear in non-adjacent rounds (e.g. special award tracks) without +-- implying the current round was finalized. +UPDATE "Round" r +SET "finalizedAt" = NOW(), "finalizedBy" = 'system-migration' +WHERE r.status IN ('ROUND_CLOSED', 'ROUND_ARCHIVED') + AND EXISTS ( + SELECT 1 + FROM "Round" next_r + JOIN "ProjectRoundState" next_prs ON next_prs."roundId" = next_r.id + JOIN "ProjectRoundState" cur_prs ON cur_prs."roundId" = r.id + AND cur_prs."projectId" = next_prs."projectId" + WHERE next_r."competitionId" = r."competitionId" + AND next_r."sortOrder" = r."sortOrder" + 1 + LIMIT 1 + ); + +-- ─── Backfill terminal states for already-finalized rounds ─────────────────── +-- These rounds were finalized manually before this system existed. +-- Set ProjectRoundState to accurate terminal states so the data matches reality. +-- All updates are guarded by current state + round type to avoid touching anything unexpected. + +-- R0 (INTAKE, closed): All 214 projects completed intake successfully → PASSED +-- Guard: only touch COMPLETED states in closed INTAKE rounds marked as finalized +UPDATE "ProjectRoundState" prs +SET state = 'PASSED', "proposedOutcome" = 'PASSED' +FROM "Round" r +WHERE prs."roundId" = r.id + AND r."roundType" = 'INTAKE' + AND r.status = 'ROUND_CLOSED' + AND r."finalizedAt" IS NOT NULL + AND prs.state = 'COMPLETED'; + +-- R1 (FILTERING, closed): Set states based on FilteringResult outcomes +-- Projects that passed filtering → PASSED +UPDATE "ProjectRoundState" prs +SET state = 'PASSED', "proposedOutcome" = 'PASSED' +FROM "Round" r +WHERE prs."roundId" = r.id + AND r."roundType" = 'FILTERING' + AND r.status = 'ROUND_CLOSED' + AND r."finalizedAt" IS NOT NULL + AND prs.state = 'PENDING' + AND EXISTS ( + SELECT 1 FROM "FilteringResult" fr + WHERE fr."projectId" = prs."projectId" + AND ( + fr."finalOutcome" = 'PASSED' + OR (fr."finalOutcome" IS NULL AND fr.outcome IN ('PASSED', 'FLAGGED')) + ) + ); + +-- Projects that were filtered out → REJECTED +UPDATE "ProjectRoundState" prs +SET state = 'REJECTED', "proposedOutcome" = 'REJECTED' +FROM "Round" r +WHERE prs."roundId" = r.id + AND r."roundType" = 'FILTERING' + AND r.status = 'ROUND_CLOSED' + AND r."finalizedAt" IS NOT NULL + AND prs.state = 'PENDING' + AND EXISTS ( + SELECT 1 FROM "FilteringResult" fr + WHERE fr."projectId" = prs."projectId" + AND ( + fr."finalOutcome" = 'FILTERED_OUT' + OR (fr."finalOutcome" IS NULL AND fr.outcome = 'FILTERED_OUT') + ) + );