feat(final-docs): judges see all teams' prior-round files; revised uploads behind admin toggle
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m47s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m47s
The finals jury needs the teams' EXISTING submissions (pitch deck, exec summary, business plan, videos from prior rounds) — which all 9 teams already have. So: - listFinalistDocumentsForReview now returns ALL of each finalist team's files across every round (labeled by doc type + round; finale uploads flagged 'Revised for finals'), with presigned URLs. NOT gated — judges always see. - Revised re-uploads are now an admin toggle (Round.configJson.allowFinalistRevisedUploads, default OFF): gates the banner/panel (getFinalDocumentStatusForProject), the upload guard (getUploadUrl/deleteFile), the documents-page round, and the reminders (manual + cron). When off, teams aren't prompted/able to upload. - finalist.get/setRevisedUploadSetting + a Switch on the admin finale overview. - judge review component rewritten to a per-team labeled file list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ import { BUCKET_NAME, generateObjectKey } from '@/lib/minio'
|
||||
const programIds: string[] = []
|
||||
|
||||
async function makeFinaleProgram(
|
||||
opts: { roundStatus?: 'ROUND_ACTIVE' | 'ROUND_DRAFT' | 'ROUND_CLOSED'; closeAt?: Date; skipRequirements?: boolean } = {},
|
||||
opts: { roundStatus?: 'ROUND_ACTIVE' | 'ROUND_DRAFT' | 'ROUND_CLOSED'; closeAt?: Date; skipRequirements?: boolean; uploadsEnabled?: boolean } = {},
|
||||
) {
|
||||
const program = await createTestProgram()
|
||||
programIds.push(program.id)
|
||||
@@ -35,6 +35,7 @@ async function makeFinaleProgram(
|
||||
status: opts.roundStatus ?? 'ROUND_ACTIVE',
|
||||
sortOrder: 6,
|
||||
windowCloseAt: opts.closeAt ?? new Date(Date.now() + 86_400_000),
|
||||
configJson: { allowFinalistRevisedUploads: opts.uploadsEnabled ?? true },
|
||||
})
|
||||
if (opts.skipRequirements) {
|
||||
return { program, comp, round, reqPlan: undefined, reqVideo: undefined }
|
||||
@@ -110,6 +111,14 @@ describe('getFinalDocumentStatusForProject', () => {
|
||||
expect(status).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null when the admin has NOT enabled revised uploads (toggle off)', async () => {
|
||||
const { program, round } = await makeFinaleProgram({ uploadsEnabled: false })
|
||||
const project = await createTestProject(program.id)
|
||||
await createTestProjectRoundState(project.id, round.id)
|
||||
const status = await getFinalDocumentStatusForProject(prisma, project.id)
|
||||
expect(status).toBeNull()
|
||||
})
|
||||
|
||||
it('reports allRequiredUploaded false when the round has no required requirements', async () => {
|
||||
const { program, round } = await makeFinaleProgram({ skipRequirements: true })
|
||||
const project = await createTestProject(program.id)
|
||||
@@ -132,7 +141,7 @@ describe('applicant.getFinalDocumentStatus', () => {
|
||||
const program = await createTestProgram()
|
||||
localPrograms.push(program.id)
|
||||
const comp = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000) })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000), configJson: { allowFinalistRevisedUploads: true } })
|
||||
await prisma.fileRequirement.create({ data: { id: uid('req'), roundId: round.id, name: 'Executive Summary', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 1 } })
|
||||
const project = await createTestProject(program.id)
|
||||
await createTestProjectRoundState(project.id, round.id)
|
||||
@@ -163,7 +172,7 @@ describe('sendManualFinalDocReminders', () => {
|
||||
const program = await createTestProgram()
|
||||
localPrograms.push(program.id)
|
||||
const comp = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000) })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000), configJson: { allowFinalistRevisedUploads: true } })
|
||||
await prisma.fileRequirement.create({ data: { id: uid('req'), roundId: round.id, name: 'Executive Summary', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 1 } })
|
||||
const project = await createTestProject(program.id)
|
||||
await createTestProjectRoundState(project.id, round.id)
|
||||
@@ -189,7 +198,7 @@ describe('sendDueFinalDocReminders', () => {
|
||||
const round = await createTestRound(comp.id, {
|
||||
roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6,
|
||||
windowCloseAt: new Date(Date.now() + 3_600_000), // 1h out → within 48h window
|
||||
configJson: { finalDocsReminderHoursBeforeDeadline: 48 },
|
||||
configJson: { finalDocsReminderHoursBeforeDeadline: 48, allowFinalistRevisedUploads: true },
|
||||
})
|
||||
await prisma.fileRequirement.create({ data: { id: uid('req'), roundId: round.id, name: 'Executive Summary', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 1 } })
|
||||
const project = await createTestProject(program.id)
|
||||
@@ -215,7 +224,7 @@ describe('finalist.listReviewDocuments', () => {
|
||||
localPrograms.push(program.id)
|
||||
const comp = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
||||
const jg = await prisma.juryGroup.create({ data: { id: uid('jg'), competitionId: comp.id, name: 'Finals Jury', slug: uid('jg') } })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000) })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000), configJson: { allowFinalistRevisedUploads: true } })
|
||||
await prisma.round.update({ where: { id: round.id }, data: { juryGroupId: jg.id } })
|
||||
await prisma.fileRequirement.create({ data: { id: uid('req'), roundId: round.id, name: 'Executive Summary', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 1 } })
|
||||
const project = await createTestProject(program.id, { competitionCategory: 'STARTUP' })
|
||||
@@ -257,7 +266,7 @@ describe('mentor.getProjectFinalDocuments', () => {
|
||||
it('returns status for a project the mentor is assigned to', async () => {
|
||||
const program = await createTestProgram(); localPrograms.push(program.id)
|
||||
const comp = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000) })
|
||||
const round = await createTestRound(comp.id, { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6, windowCloseAt: new Date(Date.now() + 86_400_000), configJson: { allowFinalistRevisedUploads: true } })
|
||||
await prisma.fileRequirement.create({ data: { id: uid('req'), roundId: round.id, name: 'Executive Summary', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 1 } })
|
||||
const project = await createTestProject(program.id)
|
||||
await createTestProjectRoundState(project.id, round.id)
|
||||
|
||||
Reference in New Issue
Block a user