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:
109
prisma/migrations/20260429002325_add_lunch_event/migration.sql
Normal file
109
prisma/migrations/20260429002325_add_lunch_event/migration.sql
Normal file
@@ -0,0 +1,109 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DietaryTag" AS ENUM ('VEGETARIAN', 'VEGAN', 'GLUTEN_FREE', 'PESCATARIAN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Allergen" AS ENUM ('GLUTEN', 'CRUSTACEANS', 'EGGS', 'FISH', 'PEANUTS', 'SOYBEANS', 'MILK', 'TREE_NUTS', 'CELERY', 'MUSTARD', 'SESAME', 'SULPHITES', 'LUPIN', 'MOLLUSCS');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "LunchEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"programId" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"eventAt" TIMESTAMP(3),
|
||||
"endAt" TIMESTAMP(3),
|
||||
"venue" TEXT,
|
||||
"notes" TEXT,
|
||||
"changeCutoffHours" INTEGER NOT NULL DEFAULT 48,
|
||||
"reminderHoursBeforeDeadline" INTEGER,
|
||||
"cronEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"extraRecipients" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"reminderSentAt" TIMESTAMP(3),
|
||||
"recapSentAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "LunchEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Dish" (
|
||||
"id" TEXT NOT NULL,
|
||||
"lunchEventId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"dietaryTags" "DietaryTag"[],
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Dish_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MemberLunchPick" (
|
||||
"id" TEXT NOT NULL,
|
||||
"attendingMemberId" TEXT NOT NULL,
|
||||
"dishId" TEXT,
|
||||
"allergens" "Allergen"[] DEFAULT ARRAY[]::"Allergen"[],
|
||||
"allergenOther" TEXT,
|
||||
"pickedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "MemberLunchPick_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ExternalAttendee" (
|
||||
"id" TEXT NOT NULL,
|
||||
"lunchEventId" TEXT NOT NULL,
|
||||
"projectId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"email" TEXT,
|
||||
"roleNote" TEXT,
|
||||
"dishId" TEXT,
|
||||
"allergens" "Allergen"[] DEFAULT ARRAY[]::"Allergen"[],
|
||||
"allergenOther" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ExternalAttendee_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "LunchEvent_programId_key" ON "LunchEvent"("programId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Dish_lunchEventId_idx" ON "Dish"("lunchEventId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "MemberLunchPick_attendingMemberId_key" ON "MemberLunchPick"("attendingMemberId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MemberLunchPick_dishId_idx" ON "MemberLunchPick"("dishId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ExternalAttendee_lunchEventId_idx" ON "ExternalAttendee"("lunchEventId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ExternalAttendee_projectId_idx" ON "ExternalAttendee"("projectId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LunchEvent" ADD CONSTRAINT "LunchEvent_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Dish" ADD CONSTRAINT "Dish_lunchEventId_fkey" FOREIGN KEY ("lunchEventId") REFERENCES "LunchEvent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MemberLunchPick" ADD CONSTRAINT "MemberLunchPick_attendingMemberId_fkey" FOREIGN KEY ("attendingMemberId") REFERENCES "AttendingMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MemberLunchPick" ADD CONSTRAINT "MemberLunchPick_dishId_fkey" FOREIGN KEY ("dishId") REFERENCES "Dish"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ExternalAttendee" ADD CONSTRAINT "ExternalAttendee_lunchEventId_fkey" FOREIGN KEY ("lunchEventId") REFERENCES "LunchEvent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ExternalAttendee" ADD CONSTRAINT "ExternalAttendee_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ExternalAttendee" ADD CONSTRAINT "ExternalAttendee_dishId_fkey" FOREIGN KEY ("dishId") REFERENCES "Dish"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user