feat(relief): cascading absence model for relief-of-relief#257
Open
guangshinhaha wants to merge 1 commit into
Open
feat(relief): cascading absence model for relief-of-relief#257guangshinhaha wants to merge 1 commit into
guangshinhaha wants to merge 1 commit into
Conversation
When a relief teacher (Teacher A) who is assigned to cover someone else also falls sick, their active assignments are now auto-cancelled and the periods surface back on the dashboard for re-assignment to Teacher C. Schema: adds ReliefAssignmentStatus enum (ACTIVE, CANCELLED_ASSIGNEE_ABSENT, CANCELLED_ADMIN), cancelledAt, and a self-referential replacedById chain on ReliefAssignment. Replaces the @@unique(timetableEntryId, date) with an app-level check so cancelled rows don't block new assignments. All query sites (dashboard, available-teachers, auto-assign, export, daily-summary, scheduling) now filter by status=ACTIVE. Run `npx prisma migrate dev --name cascading_absence_status` after pulling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds a cascading-cancellation model for “relief-of-relief” scenarios by introducing an ACTIVE/CANCELLED_* status on ReliefAssignment and updating read paths to ignore cancelled assignments so uncovered slots can re-surface for re-assignment.
Changes:
- Introduces
ReliefAssignmentStatus,cancelledAt, and a self-referential replacement chain (replacedById) onReliefAssignment. - Adds cascading cancellation when an absence is submitted for a teacher who is currently an active relief assignee.
- Updates multiple query/read sites (dashboard, auto-assign, export, etc.) to filter
ReliefAssignmentrows bystatus: "ACTIVE".
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/scheduling/absenceCoverage.ts | Treats only ACTIVE internal relief assignments as “covered” when resolving unfilled periods. |
| src/lib/cascadingAbsence.ts | Adds helper to cancel ACTIVE relief assignments for an absent relief teacher across a date range. |
| src/lib/autoAssignRules.ts | Excludes cancelled assignments from workload/shift counting. |
| src/lib/autoAssign.ts | Excludes cancelled assignments when determining which slots are already assigned. |
| src/app/api/relief-assignments/route.ts | Updates assignment creation pre-checks to consider only ACTIVE rows. |
| src/app/api/export/route.ts | Exports only ACTIVE assignments. |
| src/app/api/dashboard/route.ts | Dashboard read path now includes only ACTIVE assignments. |
| src/app/api/dashboard/available-teachers/route.ts | Availability computation ignores cancelled assignments. |
| src/app/api/cron/daily-summary/route.ts | Daily summary email counts/listing exclude cancelled assignments. |
| src/app/api/auto-assign/route.ts | Preview path excludes cancelled assignments when computing “already covering”. |
| src/app/api/absence-reports/route.ts | Triggers cascading cancellation after creating an absence report. |
| src/app/api/[slug]/preferred-relief-assignments/route.ts | Internal-assignment collision check now considers only ACTIVE assignments. |
| prisma/seed.ts | Seeds an example cancelled relief assignment and a replacement row to demonstrate the chain. |
| prisma/schema.prisma | Adds ReliefAssignment status/cancellation/replacement fields and replaces slot/date unique constraint with indexes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+65
to
+69
| // Pre-check 2: slot already has an ACTIVE assignment? | ||
| const slotTaken = await prisma.reliefAssignment.findFirst({ | ||
| where: { timetableEntryId, date, status: "ACTIVE" }, | ||
| include: { | ||
| reliefTeacher: { select: { name: true } }, |
Comment on lines
63
to
66
| prisma.reliefAssignment.findMany({ | ||
| where: { schoolId, date: dateUTC, reliefTeacherId: { in: proposedTeacherIds } }, | ||
| where: { schoolId, date: dateUTC, status: "ACTIVE", reliefTeacherId: { in: proposedTeacherIds } }, | ||
| select: { reliefTeacherId: true, timetableEntry: { select: { periodId: true } } }, | ||
| }), |
| outboundMessages OutboundMessage[] | ||
|
|
||
| @@unique([timetableEntryId, date]) | ||
| @@index([timetableEntryId, date]) |
Comment on lines
+384
to
+391
| const teacherRecord = await prisma.teacher.findFirst({ | ||
| where: { id: resolvedTeacherId, schoolId: resolvedSchoolId! }, | ||
| select: { id: true }, | ||
| }); | ||
| if (teacherRecord) { | ||
| const { cancelAssignmentsForAbsentRelief } = await import("@/lib/cascadingAbsence"); | ||
| await cancelAssignmentsForAbsentRelief(resolvedTeacherId, start, end); | ||
| } |
Comment on lines
+389
to
+390
| const { cancelAssignmentsForAbsentRelief } = await import("@/lib/cascadingAbsence"); | ||
| await cancelAssignmentsForAbsentRelief(resolvedTeacherId, start, end); |
Comment on lines
+1
to
+21
| import { prisma } from "@/lib/prisma"; | ||
|
|
||
| /** | ||
| * When a teacher submits an absence, check if they have any ACTIVE relief | ||
| * assignments on the affected dates. If so, cancel those assignments | ||
| * (CANCELLED_ASSIGNEE_ABSENT) so the periods surface back on the dashboard | ||
| * for re-assignment. | ||
| * | ||
| * Returns the IDs of cancelled assignments (empty array if none). | ||
| */ | ||
| export async function cancelAssignmentsForAbsentRelief( | ||
| teacherId: string, | ||
| startDate: Date, | ||
| endDate: Date, | ||
| ): Promise<string[]> { | ||
| const affected = await prisma.reliefAssignment.findMany({ | ||
| where: { | ||
| reliefTeacherId: teacherId, | ||
| status: "ACTIVE", | ||
| date: { gte: startDate, lte: endDate }, | ||
| }, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ReliefAssignmentStatusenum (ACTIVE,CANCELLED_ASSIGNEE_ABSENT,CANCELLED_ADMIN),cancelledAt, and self-referentialreplacedByIdchain toReliefAssignmentfor tracking cascading absencesstatus = ACTIVEso cancelled assignments don't appear as filledWhy
If Teacher A is assigned to cover Teacher B's Period 1, but Teacher A also falls sick, that period was previously stuck as "assigned" with no way to reassign. Now the system detects this automatically and releases the slot.
Migration
After pulling, run:
Test plan
npx prisma migrate dev— migration applies cleanlynpx prisma db seed) includes a cascading absence examplenpm testpasses (516/516)npm run buildpasses🤖 Generated with Claude Code