refactor(schema-cascade): rename Project.mentorAssignment → mentorAssignments
Schema dropped @unique on MentorAssignment.projectId in PR8 Task 1 →
back-relation becomes a list. Mechanical rename of Prisma queries and
consumer accessors. Legacy single-mentor callers use [0] with a TODO for
PR8 Task 8 to surface the full list. mentor-workspace.ts is left as Task 5.
- routers (mentor, project, applicant, finalist, round) and smart-assignment
service: include/where/select keys renamed; `mentorAssignment: null` →
`mentorAssignments: { none: {} }`; `{ isNot: null }` → `{ some: {} }`.
- UI consumers (mentor + applicant pages): `project.mentorAssignment` →
`project.mentorAssignments[0]` with TODO markers.
- Tests: `findUnique({ projectId })` → `findFirst({ projectId })` since the
composite key now requires both projectId+mentorId. MentorFile.create gains
the new required projectId.
- Workspace endpoints in mentor.ts now guard null mentorAssignmentId until
Task 5 re-scopes them to project.
- finalist.unconfirm now cascades to ALL active mentor assignments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,10 @@ export default function ApplicantMentorPage() {
|
||||
)
|
||||
}
|
||||
|
||||
const mentor = dashboardData?.project?.mentorAssignment?.mentor
|
||||
// TODO(PR8 Task 7): show ALL assigned mentors. For now we display only the
|
||||
// first one until the multi-mentor applicant UI ships.
|
||||
const primaryAssignment = dashboardData?.project?.mentorAssignments?.[0] ?? null
|
||||
const mentor = primaryAssignment?.mentor
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -136,9 +139,9 @@ export default function ApplicantMentorPage() {
|
||||
)}
|
||||
|
||||
{/* Files */}
|
||||
{dashboardData?.project?.mentorAssignment?.id && (
|
||||
{primaryAssignment?.id && (
|
||||
<WorkspaceFilesPanel
|
||||
mentorAssignmentId={dashboardData.project.mentorAssignment.id}
|
||||
mentorAssignmentId={primaryAssignment.id}
|
||||
asApplicant
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -357,12 +357,12 @@ export default function ApplicantProjectPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mentor info */}
|
||||
{project.mentorAssignment?.mentor && (
|
||||
{/* Mentor info — TODO(PR8 Task 7): list ALL assigned mentors */}
|
||||
{project.mentorAssignments?.[0]?.mentor && (
|
||||
<div className="rounded-lg border p-3 bg-muted/50">
|
||||
<p className="text-sm font-medium mb-1">Assigned Mentor</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{project.mentorAssignment.mentor.name} ({project.mentorAssignment.mentor.email})
|
||||
{project.mentorAssignments[0].mentor.name} ({project.mentorAssignments[0].mentor.email})
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -94,14 +94,18 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||
},
|
||||
})
|
||||
|
||||
// TODO(PR8 Task 9): show co-mentors. For now we pick the first assignment
|
||||
// to keep tracking + chat working unchanged.
|
||||
const primaryAssignment = project?.mentorAssignments?.[0] ?? null
|
||||
|
||||
// Track view when project loads
|
||||
const trackView = trpc.mentor.trackView.useMutation()
|
||||
useEffect(() => {
|
||||
if (project?.mentorAssignment?.id) {
|
||||
trackView.mutate({ mentorAssignmentId: project.mentorAssignment.id })
|
||||
if (primaryAssignment?.id) {
|
||||
trackView.mutate({ mentorAssignmentId: primaryAssignment.id })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [project?.mentorAssignment?.id])
|
||||
}, [primaryAssignment?.id])
|
||||
|
||||
if (isLoading) {
|
||||
return <ProjectDetailSkeleton />
|
||||
@@ -135,7 +139,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||
|
||||
const teamLead = project.teamMembers?.find((m) => m.role === 'LEAD')
|
||||
const otherMembers = project.teamMembers?.filter((m) => m.role !== 'LEAD') || []
|
||||
const mentorAssignment = project.mentorAssignment
|
||||
const mentorAssignment = primaryAssignment
|
||||
const mentorAssignmentId = mentorAssignment?.id
|
||||
const programId = project.program?.id
|
||||
const viewerIsAssignedMentor =
|
||||
@@ -477,7 +481,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||
<CardContent>
|
||||
<MentorChat
|
||||
messages={mentorMessages || []}
|
||||
currentUserId={project.mentorAssignment?.mentor?.id || ''}
|
||||
currentUserId={primaryAssignment?.mentor?.id || ''}
|
||||
onSendMessage={async (message) => {
|
||||
await sendMessage.mutateAsync({ projectId, message })
|
||||
}}
|
||||
|
||||
@@ -1176,7 +1176,7 @@ export const applicantRouter = router({
|
||||
],
|
||||
},
|
||||
include: {
|
||||
mentorAssignment: { select: { mentorId: true } },
|
||||
mentorAssignments: { select: { mentorId: true } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1187,7 +1187,10 @@ export const applicantRouter = router({
|
||||
})
|
||||
}
|
||||
|
||||
if (!project.mentorAssignment) {
|
||||
// TODO(PR8 Task 7): notify ALL assigned mentors. For now we notify the
|
||||
// first one for legacy parity.
|
||||
const primaryMentorAssignment = project.mentorAssignments[0] ?? null
|
||||
if (!primaryMentorAssignment) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'No mentor assigned to this project',
|
||||
@@ -1207,9 +1210,9 @@ export const applicantRouter = router({
|
||||
},
|
||||
})
|
||||
|
||||
// Notify the mentor
|
||||
// Notify the (primary) mentor
|
||||
await createNotification({
|
||||
userId: project.mentorAssignment.mentorId,
|
||||
userId: primaryMentorAssignment.mentorId,
|
||||
type: 'MENTOR_MESSAGE',
|
||||
title: 'New Message',
|
||||
message: `${ctx.user.name || 'Team member'} sent a message about "${project.title}"`,
|
||||
@@ -1313,7 +1316,7 @@ export const applicantRouter = router({
|
||||
submittedBy: {
|
||||
select: { id: true, name: true, email: true },
|
||||
},
|
||||
mentorAssignment: {
|
||||
mentorAssignments: {
|
||||
include: {
|
||||
mentor: {
|
||||
select: { id: true, name: true, email: true },
|
||||
@@ -1523,7 +1526,7 @@ export const applicantRouter = router({
|
||||
select: {
|
||||
id: true,
|
||||
programId: true,
|
||||
mentorAssignment: { select: { id: true } },
|
||||
mentorAssignments: { select: { id: true }, take: 1 },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1531,8 +1534,8 @@ export const applicantRouter = router({
|
||||
return { hasMentor: false, hasEvaluationRounds: false }
|
||||
}
|
||||
|
||||
// Check if mentor is assigned
|
||||
const hasMentor = !!project.mentorAssignment
|
||||
// Check if mentor is assigned (any active assignment counts)
|
||||
const hasMentor = project.mentorAssignments.length > 0
|
||||
|
||||
// Check if feedback is available — first check admin settings, then fall back to per-round config
|
||||
let hasEvaluationRounds = false
|
||||
@@ -2689,8 +2692,12 @@ export const applicantRouter = router({
|
||||
})
|
||||
}
|
||||
|
||||
const assignment = await ctx.prisma.mentorAssignment.findUnique({
|
||||
// TODO(PR8 Task 7): when multiple mentors are assigned, surface them all
|
||||
// in the applicant message thread. For now we display the most recently
|
||||
// assigned (non-dropped) mentor as the "primary".
|
||||
const assignment = await ctx.prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: input.projectId },
|
||||
orderBy: { assignedAt: 'desc' },
|
||||
include: { mentor: { select: { id: true, name: true, email: true } } },
|
||||
})
|
||||
|
||||
|
||||
@@ -772,7 +772,8 @@ export const finalistRouter = router({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
mentorAssignment: {
|
||||
mentorAssignments: {
|
||||
where: { droppedAt: null, completionStatus: { not: 'completed' } },
|
||||
select: {
|
||||
id: true,
|
||||
completionStatus: true,
|
||||
@@ -796,10 +797,12 @@ export const finalistRouter = router({
|
||||
data: { status: 'SUPERSEDED' },
|
||||
})
|
||||
|
||||
// Cascade: drop active mentor assignment (skip if completed or already dropped)
|
||||
const ma = confirmation.project.mentorAssignment
|
||||
// Cascade: drop ALL active mentor assignments (skip dropped/completed —
|
||||
// those were filtered out by the include `where` above). With multi-mentor
|
||||
// (PR8) we propagate the cascade to every active assignment.
|
||||
const activeAssignments = confirmation.project.mentorAssignments
|
||||
let cascadedMentorAssignment = false
|
||||
if (ma && !ma.droppedAt && ma.completionStatus !== 'completed') {
|
||||
for (const ma of activeAssignments) {
|
||||
await ctx.prisma.mentorAssignment.update({
|
||||
where: { id: ma.id },
|
||||
data: {
|
||||
@@ -833,6 +836,7 @@ export const finalistRouter = router({
|
||||
reason: input.reason,
|
||||
projectId: confirmation.projectId,
|
||||
cascadedMentorAssignment,
|
||||
cascadedAssignmentCount: activeAssignments.length,
|
||||
},
|
||||
})
|
||||
return { ok: true, cascadedMentorAssignment }
|
||||
|
||||
@@ -82,13 +82,15 @@ export const mentorRouter = router({
|
||||
const project = await ctx.prisma.project.findUniqueOrThrow({
|
||||
where: { id: input.projectId },
|
||||
include: {
|
||||
mentorAssignment: true,
|
||||
mentorAssignments: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (project.mentorAssignment) {
|
||||
// TODO(PR8 Task 8): surface all mentors. Legacy single-mentor early-return.
|
||||
const primaryMentor = project.mentorAssignments[0] ?? null
|
||||
if (primaryMentor) {
|
||||
return {
|
||||
currentMentor: project.mentorAssignment,
|
||||
currentMentor: primaryMentor,
|
||||
suggestions: [],
|
||||
source: 'ai' as const,
|
||||
message: 'Project already has a mentor assigned',
|
||||
@@ -222,10 +224,10 @@ export const mentorRouter = router({
|
||||
// Verify project exists and doesn't have a mentor
|
||||
const project = await ctx.prisma.project.findUniqueOrThrow({
|
||||
where: { id: input.projectId },
|
||||
include: { mentorAssignment: true },
|
||||
include: { mentorAssignments: { select: { id: true } } },
|
||||
})
|
||||
|
||||
if (project.mentorAssignment) {
|
||||
if (project.mentorAssignments.length > 0) {
|
||||
throw new TRPCError({
|
||||
code: 'CONFLICT',
|
||||
message: 'Project already has a mentor assigned',
|
||||
@@ -351,13 +353,16 @@ export const mentorRouter = router({
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// Verify project exists and doesn't have a mentor
|
||||
// Verify project exists and doesn't already have a mentor. Multi-mentor
|
||||
// stacking is reserved for explicit admin assignment via `mentor.assign`;
|
||||
// auto-assignment skips projects that already have at least one mentor
|
||||
// to avoid double-AI-assignments.
|
||||
const project = await ctx.prisma.project.findUniqueOrThrow({
|
||||
where: { id: input.projectId },
|
||||
include: { mentorAssignment: true },
|
||||
include: { mentorAssignments: { select: { id: true } } },
|
||||
})
|
||||
|
||||
if (project.mentorAssignment) {
|
||||
if (project.mentorAssignments.length > 0) {
|
||||
throw new TRPCError({
|
||||
code: 'CONFLICT',
|
||||
message: 'Project already has a mentor assigned',
|
||||
@@ -490,8 +495,12 @@ export const mentorRouter = router({
|
||||
unassign: adminProcedure
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const assignment = await ctx.prisma.mentorAssignment.findUnique({
|
||||
// TODO(PR8 Task 8): admin UI should specify which mentor to drop when
|
||||
// multiple are assigned. Legacy callers pass only projectId — we resolve
|
||||
// to the most-recent assignment for backward compatibility.
|
||||
const assignment = await ctx.prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: input.projectId },
|
||||
orderBy: { assignedAt: 'desc' },
|
||||
include: {
|
||||
mentor: { select: { id: true, name: true } },
|
||||
project: { select: { id: true, title: true } },
|
||||
@@ -507,7 +516,7 @@ export const mentorRouter = router({
|
||||
|
||||
// Delete assignment
|
||||
await ctx.prisma.mentorAssignment.delete({
|
||||
where: { projectId: input.projectId },
|
||||
where: { id: assignment.id },
|
||||
})
|
||||
|
||||
// Audit outside transaction so failures don't roll back the unassignment
|
||||
@@ -546,7 +555,7 @@ export const mentorRouter = router({
|
||||
const projects = await ctx.prisma.project.findMany({
|
||||
where: {
|
||||
programId: input.programId,
|
||||
mentorAssignment: null,
|
||||
mentorAssignments: { none: {} },
|
||||
wantsMentorship: true,
|
||||
},
|
||||
select: { id: true },
|
||||
@@ -716,7 +725,7 @@ export const mentorRouter = router({
|
||||
where: {
|
||||
roundId: input.roundId,
|
||||
project: {
|
||||
mentorAssignment: null,
|
||||
mentorAssignments: { none: {} },
|
||||
// Only assign mentors to projects whose team has confirmed they will
|
||||
// attend the grand finale. This skips PENDING/DECLINED/EXPIRED/SUPERSEDED
|
||||
// confirmations and any project without a confirmation row at all.
|
||||
@@ -834,7 +843,7 @@ export const mentorRouter = router({
|
||||
where: {
|
||||
roundId: input.roundId,
|
||||
project: {
|
||||
mentorAssignment: { isNot: null },
|
||||
mentorAssignments: { some: {} },
|
||||
...(eligibility === 'requested_only' ? { wantsMentorship: true } : {}),
|
||||
},
|
||||
},
|
||||
@@ -906,13 +915,13 @@ export const mentorRouter = router({
|
||||
ctx.prisma.projectRoundState.count({
|
||||
where: {
|
||||
roundId: input.roundId,
|
||||
project: { wantsMentorship: true, mentorAssignment: { isNot: null } },
|
||||
project: { wantsMentorship: true, mentorAssignments: { some: {} } },
|
||||
},
|
||||
}),
|
||||
ctx.prisma.projectRoundState.count({
|
||||
where: {
|
||||
roundId: input.roundId,
|
||||
project: { mentorAssignment: { isNot: null } },
|
||||
project: { mentorAssignments: { some: {} } },
|
||||
},
|
||||
}),
|
||||
ctx.prisma.mentorMessage.count({
|
||||
@@ -1107,7 +1116,11 @@ export const mentorRouter = router({
|
||||
status: true,
|
||||
oceanIssue: true,
|
||||
competitionCategory: true,
|
||||
mentorAssignment: {
|
||||
mentorAssignments: {
|
||||
// TODO(PR8 Task 8): surface all mentors in the activity view.
|
||||
// For now keep the legacy single-mentor activity row by picking the
|
||||
// latest-assigned, non-dropped assignment (or the most-recent overall).
|
||||
orderBy: { assignedAt: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
method: true,
|
||||
@@ -1157,7 +1170,10 @@ export const mentorRouter = router({
|
||||
|
||||
const rows = projects.map((p) => {
|
||||
// Treat a dropped mentor assignment as if no mentor is assigned.
|
||||
const ma = p.mentorAssignment && !p.mentorAssignment.droppedAt ? p.mentorAssignment : null
|
||||
// TODO(PR8 Task 8): surface all mentors. Legacy shape: pick the most
|
||||
// recent non-dropped assignment for the activity row.
|
||||
const firstActive = p.mentorAssignments.find((a) => !a.droppedAt) ?? null
|
||||
const ma = firstActive
|
||||
const lastMessageAt = ma?.messages[0]?.createdAt ?? null
|
||||
const lastFileAt = ma?.files[0]?.createdAt ?? null
|
||||
const lastActivityAt = [lastMessageAt, lastFileAt]
|
||||
@@ -1279,7 +1295,7 @@ export const mentorRouter = router({
|
||||
files: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
},
|
||||
mentorAssignment: {
|
||||
mentorAssignments: {
|
||||
include: {
|
||||
mentor: {
|
||||
select: { id: true, name: true, email: true },
|
||||
@@ -2157,6 +2173,12 @@ export const mentorRouter = router({
|
||||
select: { bucket: true, objectKey: true, fileName: true, mentorAssignmentId: true },
|
||||
})
|
||||
if (!file) throw new TRPCError({ code: 'NOT_FOUND', message: 'File not found' })
|
||||
// TODO(PR8 Task 5): re-scope workspace access from assignment to project
|
||||
// so files whose original assignment was dropped (mentorAssignmentId =
|
||||
// null) remain accessible by the team.
|
||||
if (!file.mentorAssignmentId) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Orphaned workspace file' })
|
||||
}
|
||||
await assertWorkspaceAccess(ctx.prisma, ctx.user.id, file.mentorAssignmentId)
|
||||
const url = await getPresignedUrl(file.bucket, file.objectKey, 'GET', 900,
|
||||
{ downloadFileName: file.fileName })
|
||||
@@ -2174,6 +2196,10 @@ export const mentorRouter = router({
|
||||
select: { mentorAssignmentId: true },
|
||||
})
|
||||
if (!file) throw new TRPCError({ code: 'NOT_FOUND', message: 'File not found' })
|
||||
// TODO(PR8 Task 5): re-scope workspace access from assignment to project.
|
||||
if (!file.mentorAssignmentId) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Orphaned workspace file' })
|
||||
}
|
||||
await assertWorkspaceAccess(ctx.prisma, ctx.user.id, file.mentorAssignmentId)
|
||||
try {
|
||||
await workspaceDeleteFileService(
|
||||
@@ -2209,6 +2235,10 @@ export const mentorRouter = router({
|
||||
if (!file) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'File not found' })
|
||||
}
|
||||
// TODO(PR8 Task 5): re-scope workspace access from assignment to project.
|
||||
if (!file.mentorAssignmentId) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Orphaned workspace file' })
|
||||
}
|
||||
await assertWorkspaceAccess(ctx.prisma, ctx.user.id, file.mentorAssignmentId)
|
||||
return workspaceAddFileComment(
|
||||
{
|
||||
|
||||
@@ -188,7 +188,7 @@ export const projectRouter = router({
|
||||
orClauses.push({ assignments: { some: { userId: ctx.user.id } } })
|
||||
}
|
||||
if (userHasRole(ctx.user, 'MENTOR')) {
|
||||
orClauses.push({ mentorAssignment: { mentorId: ctx.user.id } })
|
||||
orClauses.push({ mentorAssignments: { some: { mentorId: ctx.user.id } } })
|
||||
}
|
||||
if (userHasRole(ctx.user, 'APPLICANT')) {
|
||||
orClauses.push({ teamMembers: { some: { userId: ctx.user.id } } })
|
||||
@@ -511,7 +511,7 @@ export const projectRouter = router({
|
||||
},
|
||||
orderBy: { joinedAt: 'asc' },
|
||||
},
|
||||
mentorAssignment: {
|
||||
mentorAssignments: {
|
||||
include: {
|
||||
mentor: {
|
||||
select: { id: true, name: true, email: true, expertiseTags: true, profileImageKey: true, profileImageProvider: true },
|
||||
@@ -585,14 +585,18 @@ export const projectRouter = router({
|
||||
}))
|
||||
)
|
||||
|
||||
const mentorWithAvatar = project.mentorAssignment
|
||||
// TODO(PR8 Task 8): surface all mentors. For now we keep the legacy
|
||||
// single-mentor shape and just pick the first non-dropped assignment
|
||||
// so the admin UI keeps rendering without changes.
|
||||
const primaryAssignment = project.mentorAssignments[0] ?? null
|
||||
const mentorWithAvatar = primaryAssignment
|
||||
? {
|
||||
...project.mentorAssignment,
|
||||
...primaryAssignment,
|
||||
mentor: {
|
||||
...project.mentorAssignment.mentor,
|
||||
...primaryAssignment.mentor,
|
||||
avatarUrl: await getUserAvatarUrl(
|
||||
project.mentorAssignment.mentor.profileImageKey,
|
||||
project.mentorAssignment.mentor.profileImageProvider
|
||||
primaryAssignment.mentor.profileImageKey,
|
||||
primaryAssignment.mentor.profileImageProvider
|
||||
),
|
||||
},
|
||||
}
|
||||
@@ -1311,7 +1315,7 @@ export const projectRouter = router({
|
||||
},
|
||||
orderBy: { joinedAt: 'asc' },
|
||||
},
|
||||
mentorAssignment: {
|
||||
mentorAssignments: {
|
||||
include: {
|
||||
mentor: {
|
||||
select: { id: true, name: true, email: true, expertiseTags: true, profileImageKey: true, profileImageProvider: true },
|
||||
@@ -1448,18 +1452,21 @@ export const projectRouter = router({
|
||||
}
|
||||
})
|
||||
),
|
||||
projectRaw.mentorAssignment
|
||||
? (async () => ({
|
||||
...projectRaw.mentorAssignment!,
|
||||
mentor: {
|
||||
...projectRaw.mentorAssignment!.mentor,
|
||||
avatarUrl: await getUserAvatarUrl(
|
||||
projectRaw.mentorAssignment!.mentor.profileImageKey,
|
||||
projectRaw.mentorAssignment!.mentor.profileImageProvider
|
||||
),
|
||||
},
|
||||
}))()
|
||||
: Promise.resolve(null),
|
||||
// TODO(PR8 Task 8): surface all mentors. Legacy shape — pick the first.
|
||||
(async () => {
|
||||
const primaryMa = projectRaw.mentorAssignments[0] ?? null
|
||||
if (!primaryMa) return null
|
||||
return {
|
||||
...primaryMa,
|
||||
mentor: {
|
||||
...primaryMa.mentor,
|
||||
avatarUrl: await getUserAvatarUrl(
|
||||
primaryMa.mentor.profileImageKey,
|
||||
primaryMa.mentor.profileImageProvider
|
||||
),
|
||||
},
|
||||
}
|
||||
})(),
|
||||
])
|
||||
|
||||
return {
|
||||
|
||||
@@ -236,7 +236,7 @@ export const roundRouter = router({
|
||||
where: {
|
||||
roundId: input.roundId,
|
||||
project: {
|
||||
mentorAssignment: null,
|
||||
mentorAssignments: { none: {} },
|
||||
...(eligibility === 'requested_only' ? { wantsMentorship: true } : {}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -670,7 +670,7 @@ export async function getMentorSuggestionsForProject(
|
||||
projectTags: {
|
||||
include: { tag: true },
|
||||
},
|
||||
mentorAssignment: true,
|
||||
mentorAssignments: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -714,7 +714,7 @@ export async function getMentorSuggestionsForProject(
|
||||
|
||||
for (const mentor of mentors) {
|
||||
// Skip if already assigned to this project
|
||||
if (project.mentorAssignment?.mentorId === mentor.id) {
|
||||
if (project.mentorAssignments.some((ma) => ma.mentorId === mentor.id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -213,12 +213,12 @@ describe('mentor.autoAssignBulkForRound', () => {
|
||||
|
||||
expect(result.assigned).toBe(1)
|
||||
|
||||
const requestedAssigned = await prisma.mentorAssignment.findUnique({
|
||||
const requestedAssigned = await prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: projWithRequest.id },
|
||||
})
|
||||
expect(requestedAssigned).not.toBeNull()
|
||||
|
||||
const skippedNotAssigned = await prisma.mentorAssignment.findUnique({
|
||||
const skippedNotAssigned = await prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: projWithoutRequest.id },
|
||||
})
|
||||
expect(skippedNotAssigned).toBeNull()
|
||||
@@ -291,7 +291,7 @@ describe('mentor.autoAssignBulkForRound', () => {
|
||||
expect(result.assigned).toBe(1)
|
||||
expect(result.skipped).toBe(1)
|
||||
|
||||
const stillExisting = await prisma.mentorAssignment.findUnique({
|
||||
const stillExisting = await prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: projAlreadyAssigned.id },
|
||||
})
|
||||
expect(stillExisting?.mentorId).toBe(existingMentor.id) // unchanged
|
||||
@@ -377,17 +377,17 @@ describe('mentor.autoAssignBulkForRound', () => {
|
||||
|
||||
expect(result.assigned).toBe(1)
|
||||
|
||||
const confirmedAssigned = await prisma.mentorAssignment.findUnique({
|
||||
const confirmedAssigned = await prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: projConfirmed.id },
|
||||
})
|
||||
expect(confirmedAssigned).not.toBeNull()
|
||||
|
||||
const pendingAssigned = await prisma.mentorAssignment.findUnique({
|
||||
const pendingAssigned = await prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: projPending.id },
|
||||
})
|
||||
expect(pendingAssigned).toBeNull()
|
||||
|
||||
const noConfAssigned = await prisma.mentorAssignment.findUnique({
|
||||
const noConfAssigned = await prisma.mentorAssignment.findFirst({
|
||||
where: { projectId: projNoConfirmation.id },
|
||||
})
|
||||
expect(noConfAssigned).toBeNull()
|
||||
|
||||
@@ -92,6 +92,7 @@ describe('mentor.getRoundStats', () => {
|
||||
})
|
||||
await prisma.mentorFile.create({
|
||||
data: {
|
||||
projectId: projReqAssigned.id,
|
||||
mentorAssignmentId: a1.id,
|
||||
uploadedByUserId: mentor.id,
|
||||
fileName: 'plan.pdf',
|
||||
|
||||
Reference in New Issue
Block a user