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[]
|
finalistSlotQuotas FinalistSlotQuota[]
|
||||||
waitlistEntries WaitlistEntry[]
|
waitlistEntries WaitlistEntry[]
|
||||||
hotel Hotel?
|
hotel Hotel?
|
||||||
|
lunchEvent LunchEvent?
|
||||||
|
|
||||||
@@unique([name, year])
|
@@unique([name, year])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -650,8 +651,9 @@ model Project {
|
|||||||
notificationLogs NotificationLog[]
|
notificationLogs NotificationLog[]
|
||||||
|
|
||||||
// Grand-finale logistics
|
// Grand-finale logistics
|
||||||
waitlistEntry WaitlistEntry?
|
waitlistEntry WaitlistEntry?
|
||||||
finalistConfirmation FinalistConfirmation?
|
finalistConfirmation FinalistConfirmation?
|
||||||
|
externalLunchAttendees ExternalAttendee[]
|
||||||
|
|
||||||
@@index([programId])
|
@@index([programId])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -2731,6 +2733,7 @@ model AttendingMember {
|
|||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
flightDetail FlightDetail?
|
flightDetail FlightDetail?
|
||||||
visaApplication VisaApplication?
|
visaApplication VisaApplication?
|
||||||
|
lunchPick MemberLunchPick?
|
||||||
|
|
||||||
@@unique([confirmationId, userId])
|
@@unique([confirmationId, userId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@ -2809,3 +2812,110 @@ model VisaApplication {
|
|||||||
|
|
||||||
@@index([status])
|
@@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