feat(final-docs): grand-final document status service

This commit is contained in:
Matt
2026-06-09 15:09:50 +02:00
parent b757aae551
commit b1923cf0e1
2 changed files with 173 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
import type { PrismaClient } from '@prisma/client'
export type FinalDocRequirement = {
id: string
name: string
acceptedMimeTypes: string[]
isRequired: boolean
uploaded: boolean
file: { id: string; fileName: string; mimeType: string; bucket: string; objectKey: string; createdAt: Date } | null
}
export type FinalDocumentStatus = {
roundId: string
roundName: string
deadline: Date | null
deadlinePassed: boolean
requirements: FinalDocRequirement[]
allRequiredUploaded: boolean
}
/** Resolve the program's active LIVE_FINAL round, or null. */
export async function getActiveFinaleRound(prisma: PrismaClient, programId: string) {
return prisma.round.findFirst({
where: { competition: { programId }, roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE' },
orderBy: { sortOrder: 'desc' },
select: { id: true, name: true, windowCloseAt: true },
})
}
/**
* Per-project grand-final document status. Returns null unless the project is
* enrolled (ProjectRoundState) in the program's active LIVE_FINAL round.
*/
export async function getFinalDocumentStatusForProject(
prisma: PrismaClient,
projectId: string,
): Promise<FinalDocumentStatus | null> {
const project = await prisma.project.findUnique({
where: { id: projectId },
select: { id: true, programId: true },
})
if (!project) return null
const round = await getActiveFinaleRound(prisma, project.programId)
if (!round) return null
const enrolled = await prisma.projectRoundState.findFirst({
where: { projectId, roundId: round.id },
select: { id: true },
})
if (!enrolled) return null
const requirements = await prisma.fileRequirement.findMany({
where: { roundId: round.id },
orderBy: { sortOrder: 'asc' },
select: { id: true, name: true, acceptedMimeTypes: true, isRequired: true },
})
const files = await prisma.projectFile.findMany({
where: { projectId, requirementId: { in: requirements.map((r) => r.id) } },
orderBy: { createdAt: 'desc' },
select: { id: true, requirementId: true, fileName: true, mimeType: true, bucket: true, objectKey: true, createdAt: true },
})
const fileByReq = new Map<string, (typeof files)[number]>()
for (const f of files) if (f.requirementId && !fileByReq.has(f.requirementId)) fileByReq.set(f.requirementId, f)
const reqStatuses: FinalDocRequirement[] = requirements.map((r) => {
const f = fileByReq.get(r.id) ?? null
return {
id: r.id, name: r.name, acceptedMimeTypes: r.acceptedMimeTypes, isRequired: r.isRequired,
uploaded: !!f,
file: f ? { id: f.id, fileName: f.fileName, mimeType: f.mimeType, bucket: f.bucket, objectKey: f.objectKey, createdAt: f.createdAt } : null,
}
})
const allRequiredUploaded = reqStatuses.filter((r) => r.isRequired).every((r) => r.uploaded)
const deadline = round.windowCloseAt ?? null
return {
roundId: round.id,
roundName: round.name,
deadline,
deadlinePassed: deadline ? new Date() > deadline : false,
requirements: reqStatuses,
allRequiredUploaded,
}
}