diff --git a/src/server/routers/applicant.ts b/src/server/routers/applicant.ts index 4a42772..845dd5e 100644 --- a/src/server/routers/applicant.ts +++ b/src/server/routers/applicant.ts @@ -9,6 +9,7 @@ import { sendStyledNotificationEmail, sendTeamMemberInviteEmail } from '@/lib/em import { logAudit } from '@/server/utils/audit' import { createNotification } from '../services/in-app-notification' import { checkRequirementsAndTransition, triggerInProgressOnActivity, transitionProject, isTerminalState } from '../services/round-engine' +import { getFinalDocumentStatusForProject } from '../services/final-documents' import { EvaluationConfigSchema, MentoringConfigSchema } from '@/types/competition-configs' import type { PrismaClient, Prisma, RoundType } from '@prisma/client' @@ -1521,6 +1522,22 @@ export const applicantRouter = router({ } }), + /** Grand-final document status for the caller's project (banner + mentor panel). */ + getFinalDocumentStatus: protectedProcedure.query(async ({ ctx }) => { + const project = await ctx.prisma.project.findFirst({ + where: { + OR: [ + { submittedByUserId: ctx.user.id }, + { teamMembers: { some: { userId: ctx.user.id } } }, + ], + }, + orderBy: { createdAt: 'desc' }, + select: { id: true }, + }) + if (!project) return null + return getFinalDocumentStatusForProject(ctx.prisma, project.id) + }), + /** * Lightweight flags for conditional nav rendering. */ diff --git a/tests/unit/final-documents.test.ts b/tests/unit/final-documents.test.ts index c855f51..fd65871 100644 --- a/tests/unit/final-documents.test.ts +++ b/tests/unit/final-documents.test.ts @@ -6,10 +6,13 @@ import { createTestRound, createTestProject, createTestProjectRoundState, + createTestUser, cleanupTestData, uid, } from '../helpers' import { getFinalDocumentStatusForProject } from '@/server/services/final-documents' +import * as applicantRouter from '@/server/routers/applicant' +import { createCaller } from '../setup' const programIds: string[] = [] @@ -100,3 +103,36 @@ describe('getFinalDocumentStatusForProject', () => { expect(status!.allRequiredUploaded).toBe(false) }) }) + +describe('applicant.getFinalDocumentStatus', () => { + const localPrograms: string[] = [] + const localUsers: string[] = [] + afterAll(async () => { + for (const id of localPrograms) await cleanupTestData(id, localUsers) + }) + + it('returns the status for the caller\'s enrolled finalist project', 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) }) + 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) + const user = await createTestUser('APPLICANT') + localUsers.push(user.id) + await prisma.teamMember.create({ data: { projectId: project.id, userId: user.id, role: 'LEAD' } }) + + const caller = createCaller(applicantRouter.applicantRouter, user) + const status = await caller.getFinalDocumentStatus() + expect(status?.roundId).toBe(round.id) + expect(status?.requirements).toHaveLength(1) + }) + + it('returns null when the caller has no project', async () => { + const user = await createTestUser('APPLICANT') + localUsers.push(user.id) + const caller = createCaller(applicantRouter.applicantRouter, user) + expect(await caller.getFinalDocumentStatus()).toBeNull() + }) +})