diff --git a/src/components/admin/grand-finale/final-docs-uploads-toggle.tsx b/src/components/admin/grand-finale/final-docs-uploads-toggle.tsx
new file mode 100644
index 0000000..f2119d2
--- /dev/null
+++ b/src/components/admin/grand-finale/final-docs-uploads-toggle.tsx
@@ -0,0 +1,37 @@
+'use client'
+
+import { trpc } from '@/lib/trpc/client'
+import { Switch } from '@/components/ui/switch'
+import { Label } from '@/components/ui/label'
+import { toast } from 'sonner'
+
+/**
+ * Admin toggle: whether finalist teams may upload *revised* grand-final documents.
+ * Off by default — judges always see the teams' existing prior-round submissions
+ * regardless; this only controls whether teams are prompted/allowed to upload new
+ * revised versions (and whether the upload reminder cron runs).
+ */
+export function FinalDocsUploadsToggle({ roundId }: { roundId: string }) {
+ const utils = trpc.useUtils()
+ const { data } = trpc.finalist.getRevisedUploadSetting.useQuery({ roundId })
+ const set = trpc.finalist.setRevisedUploadSetting.useMutation({
+ onSuccess: (r) => {
+ toast.success(r.enabled ? 'Finalist revised uploads enabled' : 'Finalist revised uploads disabled')
+ utils.finalist.getRevisedUploadSetting.invalidate({ roundId })
+ },
+ onError: (e) => toast.error(e.message),
+ })
+ return (
+
+ set.mutate({ roundId, enabled: v })}
+ />
+
+ Allow finalists to upload revised documents
+
+
+ )
+}
diff --git a/src/components/finals/finals-documents-review.tsx b/src/components/finals/finals-documents-review.tsx
index 0f59293..cb6c913 100644
--- a/src/components/finals/finals-documents-review.tsx
+++ b/src/components/finals/finals-documents-review.tsx
@@ -60,10 +60,6 @@ export function FinalsDocumentsReview() {
)
}
- const fmt = new Intl.DateTimeFormat(undefined, {
- dateStyle: 'long',
- timeStyle: 'short',
- })
return (
@@ -71,10 +67,8 @@ export function FinalsDocumentsReview() {
Finalist Documents
- {data.submittedCount} of {data.totalCount} teams complete
- {data.round.deadline
- ? ` · due ${fmt.format(new Date(data.round.deadline))}`
- : ''}
+ {data.totalCount} finalist team{data.totalCount === 1 ? '' : 's'} · every file each team
+ has submitted across all rounds
{data.teams.map((team) => (
@@ -82,61 +76,45 @@ export function FinalsDocumentsReview() {
{team.teamName}
- {team.category && (
- {team.category}
- )}
-
- {team.submitted ? 'Complete' : 'Incomplete'}
+ {team.category && {team.category} }
+
+ {team.files.length} file{team.files.length === 1 ? '' : 's'}
- {team.documents.map((doc) => (
-
-
-
- {doc.requirementName}
+ {team.files.length === 0 && (
+
+ No files submitted.
+
+ )}
+ {team.files.map((f) => (
+
+
+
+
+ {f.roundLabel}
+
+ {f.isFinaleUpload && (
+
+ Revised for finals
+
)}
- {doc.file ? (
-
- ) : (
-
- Not yet uploaded
-
- )}
+
))}
diff --git a/src/server/routers/applicant.ts b/src/server/routers/applicant.ts
index 1330080..f9b6f3d 100644
--- a/src/server/routers/applicant.ts
+++ b/src/server/routers/applicant.ts
@@ -9,7 +9,7 @@ import { sendStyledNotificationEmail, sendTeamMemberInviteEmail } from '@/lib/em
import { logAudit } from '@/server/utils/audit'
import { createNotification, notifyProjectMentors, NotificationTypes } from '../services/in-app-notification'
import { checkRequirementsAndTransition, triggerInProgressOnActivity, transitionProject, isTerminalState } from '../services/round-engine'
-import { getFinalDocumentStatusForProject } from '../services/final-documents'
+import { getFinalDocumentStatusForProject, finalistUploadsEnabled } from '../services/final-documents'
import { EvaluationConfigSchema, MentoringConfigSchema } from '@/types/competition-configs'
import type { PrismaClient, Prisma, RoundType } from '@prisma/client'
@@ -337,12 +337,15 @@ export const applicantRouter = router({
if (input.roundId) {
const round = await ctx.prisma.round.findUnique({
where: { id: input.roundId },
- select: { name: true, status: true, roundType: true, finalizedAt: true },
+ select: { name: true, status: true, roundType: true, finalizedAt: true, configJson: true },
})
if (round) {
const uploadable =
- round.status === 'ROUND_ACTIVE' ||
- (round.roundType === 'LIVE_FINAL' && round.status === 'ROUND_DRAFT' && !round.finalizedAt)
+ round.roundType === 'LIVE_FINAL'
+ ? !round.finalizedAt &&
+ (round.status === 'ROUND_DRAFT' || round.status === 'ROUND_ACTIVE') &&
+ finalistUploadsEnabled(round.configJson)
+ : round.status === 'ROUND_ACTIVE'
if (!uploadable) {
throw new TRPCError({
code: 'BAD_REQUEST',
@@ -568,12 +571,15 @@ export const applicantRouter = router({
if (file.roundId) {
const round = await ctx.prisma.round.findUnique({
where: { id: file.roundId },
- select: { status: true, roundType: true, finalizedAt: true },
+ select: { status: true, roundType: true, finalizedAt: true, configJson: true },
})
if (round) {
const modifiable =
- round.status === 'ROUND_ACTIVE' ||
- (round.roundType === 'LIVE_FINAL' && round.status === 'ROUND_DRAFT' && !round.finalizedAt)
+ round.roundType === 'LIVE_FINAL'
+ ? !round.finalizedAt &&
+ (round.status === 'ROUND_DRAFT' || round.status === 'ROUND_ACTIVE') &&
+ finalistUploadsEnabled(round.configJson)
+ : round.status === 'ROUND_ACTIVE'
if (!modifiable) {
throw new TRPCError({
code: 'BAD_REQUEST',
@@ -1472,6 +1478,7 @@ export const applicantRouter = router({
slug: true,
roundType: true,
windowCloseAt: true,
+ configJson: true,
specialAwardId: true,
specialAward: { select: { name: true } },
},
@@ -1490,8 +1497,9 @@ export const applicantRouter = router({
openRounds = allActiveRounds
.filter((r) => {
- // LIVE_FINAL (grand-final documents) only shows to enrolled finalists.
- if (r.roundType === 'LIVE_FINAL' && !projectRoundIds.has(r.id)) return false
+ // LIVE_FINAL (grand-final documents) only shows to enrolled finalists,
+ // and only when the admin has enabled revised uploads.
+ if (r.roundType === 'LIVE_FINAL' && (!projectRoundIds.has(r.id) || !finalistUploadsEnabled(r.configJson))) return false
// Award round project isn't in → hide
if (r.specialAwardId && !projectRoundIds.has(r.id)) return false
// Main round when project is in award track and has no state in this round → hide
diff --git a/src/server/routers/finalist.ts b/src/server/routers/finalist.ts
index 1c6cec8..73c1ad0 100644
--- a/src/server/routers/finalist.ts
+++ b/src/server/routers/finalist.ts
@@ -1690,4 +1690,37 @@ export const finalistRouter = router({
if (!allowed) throw new TRPCError({ code: 'FORBIDDEN', message: 'You do not have access to the finalist documents review.' })
return listFinalistDocumentsForReview(ctx.prisma, input.programId)
}),
+
+ /** Read whether finalists may upload revised grand-final documents (admin toggle). */
+ getRevisedUploadSetting: adminProcedure
+ .input(z.object({ roundId: z.string() }))
+ .query(async ({ ctx, input }) => {
+ const round = await ctx.prisma.round.findUnique({ where: { id: input.roundId }, select: { configJson: true } })
+ const cfg = (round?.configJson ?? {}) as { allowFinalistRevisedUploads?: boolean }
+ return { enabled: !!cfg.allowFinalistRevisedUploads }
+ }),
+
+ /** Toggle whether finalists may upload revised grand-final documents (admin setting on the LIVE_FINAL round). */
+ setRevisedUploadSetting: adminProcedure
+ .input(z.object({ roundId: z.string(), enabled: z.boolean() }))
+ .mutation(async ({ ctx, input }) => {
+ const round = await ctx.prisma.round.findUnique({ where: { id: input.roundId }, select: { configJson: true, roundType: true } })
+ if (!round || round.roundType !== 'LIVE_FINAL') {
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'Not a grand-final round' })
+ }
+ const cfg = (round.configJson ?? {}) as Record
+ await ctx.prisma.round.update({
+ where: { id: input.roundId },
+ data: { configJson: { ...cfg, allowFinalistRevisedUploads: input.enabled } },
+ })
+ await logAudit({
+ prisma: ctx.prisma,
+ userId: ctx.user.id,
+ action: 'FINALIST_REVISED_UPLOADS_TOGGLED',
+ entityType: 'Round',
+ entityId: input.roundId,
+ detailsJson: { enabled: input.enabled },
+ })
+ return { ok: true, enabled: input.enabled }
+ }),
})
diff --git a/src/server/services/final-documents.ts b/src/server/services/final-documents.ts
index 797bd29..e0a9078 100644
--- a/src/server/services/final-documents.ts
+++ b/src/server/services/final-documents.ts
@@ -31,10 +31,20 @@ export async function getOpenFinaleRound(prisma: PrismaClient, programId: string
return prisma.round.findFirst({
where: { competition: { programId }, roundType: 'LIVE_FINAL', status: { in: OPEN_FINALE_STATUS }, finalizedAt: null },
orderBy: { sortOrder: 'desc' },
- select: { id: true, name: true, windowCloseAt: true },
+ select: { id: true, name: true, windowCloseAt: true, configJson: true },
})
}
+/**
+ * Whether finalist teams are allowed to upload *revised* documents for the
+ * grand final. This is an admin toggle on the LIVE_FINAL round's configJson.
+ * When false (default), judges still see the teams' existing prior-round
+ * submissions, but teams are not prompted/able to upload anything new.
+ */
+export function finalistUploadsEnabled(configJson: unknown): boolean {
+ return !!(configJson as { allowFinalistRevisedUploads?: boolean } | null)?.allowFinalistRevisedUploads
+}
+
/**
* Per-project grand-final document status. Returns null unless the project is
* enrolled (ProjectRoundState) in the program's active LIVE_FINAL round.
@@ -50,7 +60,8 @@ export async function getFinalDocumentStatusForProject(
if (!project) return null
const round = await getOpenFinaleRound(prisma, project.programId)
- if (!round) return null
+ // Banner / upload status only applies when the admin has enabled revised uploads.
+ if (!round || !finalistUploadsEnabled(round.configJson)) return null
const enrolled = await prisma.projectRoundState.findFirst({
where: { projectId, roundId: round.id },
@@ -125,7 +136,7 @@ export async function sendManualFinalDocReminders(
opts: { programId: string; projectIds?: string[]; actorId: string },
): Promise<{ sent: number }> {
const round = await getOpenFinaleRound(prisma, opts.programId)
- if (!round) return { sent: 0 }
+ if (!round || !finalistUploadsEnabled(round.configJson)) return { sent: 0 }
const states = await prisma.projectRoundState.findMany({
where: { roundId: round.id, ...(opts.projectIds ? { projectId: { in: opts.projectIds } } : {}) },
@@ -170,6 +181,8 @@ export async function sendDueFinalDocReminders(prisma: PrismaClient): Promise<{
let remindersSent = 0
for (const round of rounds) {
if (!round.windowCloseAt) continue
+ // Only chase teams to upload when the admin has enabled revised uploads.
+ if (!finalistUploadsEnabled(round.configJson)) continue
const cfg = (round.configJson ?? {}) as { finalDocsReminderHoursBeforeDeadline?: number }
const windowMs = (cfg.finalDocsReminderHoursBeforeDeadline ?? 48) * 3_600_000
const isDue = round.windowCloseAt.getTime() <= now.getTime() + windowMs && round.windowCloseAt.getTime() > now.getTime()
@@ -206,62 +219,99 @@ export async function sendDueFinalDocReminders(prisma: PrismaClient): Promise<{
return { remindersSent }
}
-export type ReviewDocument = { requirementId: string; requirementName: string; file: { id: string; fileName: string; mimeType: string; url: string } | null }
-export type ReviewTeam = { projectId: string; teamName: string; category: string | null; documents: ReviewDocument[]; submitted: boolean }
-export type ReviewPayload = { round: { id: string; name: string; deadline: Date | null }; totalCount: number; submittedCount: number; teams: ReviewTeam[] }
+export type ReviewFile = {
+ id: string
+ fileName: string
+ mimeType: string
+ url: string
+ docLabel: string // requirement name if known, else a humanized file type
+ roundLabel: string // which round this was submitted in
+ roundSort: number
+ isFinaleUpload: boolean // uploaded directly to the LIVE_FINAL round (a revised "final")
+ createdAt: Date
+}
+export type ReviewTeam = { projectId: string; teamName: string; category: string | null; files: ReviewFile[] }
+export type ReviewPayload = { round: { id: string; name: string; deadline: Date | null }; totalCount: number; teams: ReviewTeam[] }
+
+function humanizeFileType(t: string | null | undefined): string {
+ switch (t) {
+ case 'EXEC_SUMMARY': return 'Executive Summary'
+ case 'BUSINESS_PLAN': return 'Business Plan'
+ case 'PRESENTATION': return 'Presentation'
+ case 'VIDEO_PITCH': return 'Video Pitch'
+ case 'VIDEO': return 'Video'
+ case 'SUPPORTING_DOC': return 'Supporting Document'
+ default: return 'Document'
+ }
+}
/**
- * Read-only review payload of every finalist team enrolled in the program's
- * active LIVE_FINAL round, with their uploaded grand-final documents. Each
- * present file carries a server-generated GET presigned URL (1h) so finale
- * judges — who are not assignment-gated through file.getDownloadUrl — can open
- * the documents directly in the browser.
+ * Read-only review payload for finale judges: every finalist team enrolled in
+ * the program's LIVE_FINAL round, with ALL of their submitted files across every
+ * round (pitch deck, executive summary, business plan, videos, plus any revised
+ * finals uploads). Each file carries a server-generated GET presigned URL (1h)
+ * so finale judges — who are not assignment-gated through file.getDownloadUrl —
+ * can open the documents directly. This is NOT gated on the upload toggle:
+ * judges can always review the teams' existing submissions.
*/
export async function listFinalistDocumentsForReview(prisma: PrismaClient, programId: string): Promise {
const round = await getOpenFinaleRound(prisma, programId)
- if (!round) return { round: { id: '', name: '', deadline: null }, totalCount: 0, submittedCount: 0, teams: [] }
+ if (!round) return { round: { id: '', name: '', deadline: null }, totalCount: 0, teams: [] }
- const requirements = await prisma.fileRequirement.findMany({ where: { roundId: round.id }, orderBy: { sortOrder: 'asc' }, select: { id: true, name: true } })
const states = await prisma.projectRoundState.findMany({
where: { roundId: round.id },
select: { project: { select: { id: true, title: true, teamName: true, competitionCategory: true } } },
})
+ const projectIds = states.map((s) => s.project.id)
- const teams: ReviewTeam[] = []
- for (const { project } of states) {
- const files = await prisma.projectFile.findMany({
- where: { projectId: project.id, roundId: round.id, requirementId: { in: requirements.map((r) => r.id) } },
- orderBy: { createdAt: 'desc' },
- select: { id: true, requirementId: true, fileName: true, mimeType: true, bucket: true, objectKey: true },
- })
- const byReq = new Map()
- for (const f of files) if (f.requirementId && !byReq.has(f.requirementId)) byReq.set(f.requirementId, f)
+ // Every file these teams have submitted, in any round.
+ const allFiles = await prisma.projectFile.findMany({
+ where: { projectId: { in: projectIds } },
+ orderBy: { createdAt: 'desc' },
+ select: {
+ id: true, projectId: true, fileName: true, mimeType: true, fileType: true,
+ bucket: true, objectKey: true, createdAt: true, roundId: true,
+ requirement: { select: { name: true, round: { select: { name: true, sortOrder: true } } } },
+ },
+ })
- const documents: ReviewDocument[] = []
- for (const r of requirements) {
- const f = byReq.get(r.id)
- documents.push({
- requirementId: r.id,
- requirementName: r.name,
- file: f ? { id: f.id, fileName: f.fileName, mimeType: f.mimeType, url: await getPresignedUrl(f.bucket, f.objectKey, 'GET', 3600) } : null,
- })
+ // Resolve round names for files attached directly to a round (no requirement).
+ const directRoundIds = [...new Set(allFiles.filter((f) => f.roundId && !f.requirement).map((f) => f.roundId!))]
+ const directRounds = directRoundIds.length
+ ? await prisma.round.findMany({ where: { id: { in: directRoundIds } }, select: { id: true, name: true, sortOrder: true } })
+ : []
+ const roundById = new Map(directRounds.map((r) => [r.id, r]))
+
+ const filesByProject = new Map()
+ for (const f of allFiles) {
+ const r = f.requirement?.round ?? (f.roundId ? roundById.get(f.roundId) : null)
+ const rf: ReviewFile = {
+ id: f.id,
+ fileName: f.fileName,
+ mimeType: f.mimeType,
+ url: await getPresignedUrl(f.bucket, f.objectKey, 'GET', 3600),
+ docLabel: f.requirement?.name?.trim() || humanizeFileType(f.fileType),
+ roundLabel: r?.name ?? '—',
+ roundSort: r?.sortOrder ?? -1,
+ isFinaleUpload: f.roundId === round.id,
+ createdAt: f.createdAt,
}
- teams.push({
- projectId: project.id,
- teamName: project.teamName || project.title,
- category: project.competitionCategory,
- documents,
- submitted: documents.every((d) => d.file !== null),
- })
+ const list = filesByProject.get(f.projectId)
+ if (list) list.push(rf)
+ else filesByProject.set(f.projectId, [rf])
}
+ const teams: ReviewTeam[] = states.map(({ project }) => ({
+ projectId: project.id,
+ teamName: project.teamName || project.title,
+ category: project.competitionCategory,
+ files: (filesByProject.get(project.id) ?? []).sort(
+ (a, b) => b.roundSort - a.roundSort || a.docLabel.localeCompare(b.docLabel),
+ ),
+ }))
+
teams.sort((a, b) => (a.category || '').localeCompare(b.category || '') || a.teamName.localeCompare(b.teamName))
- return {
- round: { id: round.id, name: round.name, deadline: round.windowCloseAt ?? null },
- totalCount: teams.length,
- submittedCount: teams.filter((t) => t.submitted).length,
- teams,
- }
+ return { round: { id: round.id, name: round.name, deadline: round.windowCloseAt ?? null }, totalCount: teams.length, teams }
}
/** True if user is admin or a member of the program's open LIVE_FINAL jury group (DRAFT or ACTIVE). */
diff --git a/tests/unit/final-documents.test.ts b/tests/unit/final-documents.test.ts
index 2921437..5829448 100644
--- a/tests/unit/final-documents.test.ts
+++ b/tests/unit/final-documents.test.ts
@@ -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)