feat: schema for lunch event, dishes, picks, externals

Adds LunchEvent (1:1 per Program), Dish, MemberLunchPick (1:1 per
AttendingMember), ExternalAttendee + DietaryTag/Allergen enums.
Allergens use the EU 14 regulated list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-29 02:23:55 +02:00
parent e16039142e
commit cdb18cc3d1
2 changed files with 221 additions and 2 deletions

View File

@@ -507,6 +507,7 @@ model Program {
finalistSlotQuotas FinalistSlotQuota[]
waitlistEntries WaitlistEntry[]
hotel Hotel?
lunchEvent LunchEvent?
@@unique([name, year])
@@index([status])
@@ -650,8 +651,9 @@ model Project {
notificationLogs NotificationLog[]
// Grand-finale logistics
waitlistEntry WaitlistEntry?
finalistConfirmation FinalistConfirmation?
waitlistEntry WaitlistEntry?
finalistConfirmation FinalistConfirmation?
externalLunchAttendees ExternalAttendee[]
@@index([programId])
@@index([status])
@@ -2731,6 +2733,7 @@ model AttendingMember {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
flightDetail FlightDetail?
visaApplication VisaApplication?
lunchPick MemberLunchPick?
@@unique([confirmationId, userId])
@@index([userId])
@@ -2809,3 +2812,110 @@ model VisaApplication {
@@index([status])
}
// ─────────────────────────────────────────────────────────────────────────────
// Grand-finale lunch event (PR 6)
// Single configurable lunch event per edition. Each attending member has a
// 1:1 MemberLunchPick (auto-created via lunch-pick-sync). External attendees
// can be standalone or attached to a finalist project. Allergens use the
// EU 14 regulated list; dishes carry dietary tags.
// ─────────────────────────────────────────────────────────────────────────────
enum DietaryTag {
VEGETARIAN
VEGAN
GLUTEN_FREE
PESCATARIAN
}
enum Allergen {
GLUTEN
CRUSTACEANS
EGGS
FISH
PEANUTS
SOYBEANS
MILK
TREE_NUTS
CELERY
MUSTARD
SESAME
SULPHITES
LUPIN
MOLLUSCS
}
model LunchEvent {
id String @id @default(cuid())
programId String @unique
enabled Boolean @default(false)
eventAt DateTime?
endAt DateTime?
venue String?
notes String? @db.Text
changeCutoffHours Int @default(48)
reminderHoursBeforeDeadline Int?
cronEnabled Boolean @default(true)
extraRecipients String[] @default([])
reminderSentAt DateTime?
recapSentAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
dishes Dish[]
externalAttendees ExternalAttendee[]
}
model Dish {
id String @id @default(cuid())
lunchEventId String
name String
sortOrder Int @default(0)
dietaryTags DietaryTag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lunchEvent LunchEvent @relation(fields: [lunchEventId], references: [id], onDelete: Cascade)
memberPicks MemberLunchPick[]
externals ExternalAttendee[]
@@index([lunchEventId])
}
model MemberLunchPick {
id String @id @default(cuid())
attendingMemberId String @unique
dishId String?
allergens Allergen[] @default([])
allergenOther String?
pickedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
attendingMember AttendingMember @relation(fields: [attendingMemberId], references: [id], onDelete: Cascade)
dish Dish? @relation(fields: [dishId], references: [id], onDelete: SetNull)
@@index([dishId])
}
model ExternalAttendee {
id String @id @default(cuid())
lunchEventId String
projectId String?
name String
email String?
roleNote String?
dishId String?
allergens Allergen[] @default([])
allergenOther String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lunchEvent LunchEvent @relation(fields: [lunchEventId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
dish Dish? @relation(fields: [dishId], references: [id], onDelete: SetNull)
@@index([lunchEventId])
@@index([projectId])
}