feat(final-docs): grand-final document status service
This commit is contained in:
86
src/server/services/final-documents.ts
Normal file
86
src/server/services/final-documents.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user