import { AbsenceReason, AuditTargetType, EventParticipationStatus, InvitationStatus, NotdienstAlertStatus, NotdienstPlanStatus, PrismaClient, TerminStatus, TerminType, UserRole, } from "@prisma/client"; import { hash } from "bcryptjs"; // ===================================================================== // Kita-Planer ยท Dev-Seed // --------------------------------------------------------------------- // Default: nicht destruktiv. Seed-Daten werden nur angelegt, wenn die DB // noch keine Kita und keine User enthaelt. // // Reset: `npm run db:seed:reset` loescht gezielt die Demo-Daten und baut // sie neu auf. Production wird nur mit ALLOW_DATABASE_SEED=true erlaubt. // ===================================================================== const prisma = new PrismaClient(); const DEMO_KITA_SLUG = "waldameisen"; const DEFAULT_PASSWORD = "password123"; const PRIVACY_POLICY_VERSION = "2026-05-01"; const RESET_MODE = process.argv.includes("--reset"); const DEMO_USER_EMAILS = [ "super@kita-planer.local", "admin@waldameisen.local", "erzieher@waldameisen.local", "mueller@waldameisen.local", "schmidt@waldameisen.local", "yilmaz@waldameisen.local", "pending@waldameisen.local", "fischer@waldameisen.local", "kowalski@waldameisen.local", "weber@waldameisen.local", ]; const DEMO_VERIFICATION_TOKENS = [ "demo-pending-parent-token", "demo-invite-token", ]; type SeedContext = Awaited>; function dateOnly(date: Date) { return new Date(date.getFullYear(), date.getMonth(), date.getDate()); } function addDays(date: Date, days: number) { const next = new Date(date); next.setDate(next.getDate() + days); return dateOnly(next); } function addWeeks(date: Date, weeks: number) { return addDays(date, weeks * 7); } function startOfIsoWeek(date: Date) { const normalized = dateOnly(date); const day = normalized.getDay() || 7; return addDays(normalized, 1 - day); } function startOfMonth(date: Date) { return new Date(date.getFullYear(), date.getMonth(), 1); } function getPreviousMonth(date: Date) { return new Date(date.getFullYear(), date.getMonth() - 1, 1); } function getLastDayOfPreviousMonth(date: Date) { return dateOnly(new Date(date.getFullYear(), date.getMonth(), 0)); } function getTargetMonth(date: Date) { return new Date(date.getFullYear(), date.getMonth() + 1, 1); } function getWorkingDays(year: number, month: number, limit?: number) { const days: Date[] = []; const cursor = new Date(year, month - 1, 1); while (cursor.getMonth() === month - 1) { const day = cursor.getDay(); if (day !== 0 && day !== 6) { days.push(dateOnly(cursor)); } cursor.setDate(cursor.getDate() + 1); } return typeof limit === "number" ? days.slice(0, limit) : days; } function isSeedAllowed() { if ( process.env.NODE_ENV === "development" || process.env.ALLOW_DATABASE_SEED === "true" ) { return true; } console.log( "Seed uebersprungen: NODE_ENV ist nicht development. Setze ALLOW_DATABASE_SEED=true, wenn das bewusst gewuenscht ist.", ); return false; } async function isDatabaseEmpty() { const [kitaCount, userCount] = await Promise.all([ prisma.kita.count(), prisma.user.count(), ]); return kitaCount === 0 && userCount === 0; } async function resetDemoData() { const demoUsers = await prisma.user.findMany({ where: { OR: [ { email: { in: DEMO_USER_EMAILS } }, { kita: { slug: DEMO_KITA_SLUG } }, ], }, select: { id: true }, }); await prisma.verificationToken.deleteMany({ where: { OR: [ { token: { in: DEMO_VERIFICATION_TOKENS } }, { identifier: { in: demoUsers.map((user) => user.id) } }, ], }, }); await prisma.kita.deleteMany({ where: { slug: DEMO_KITA_SLUG } }); await prisma.user.deleteMany({ where: { email: { in: DEMO_USER_EMAILS } } }); } async function createDemoUsers(passwordHash: string, consentAt: Date) { const superAdmin = await prisma.user.create({ data: { email: "super@kita-planer.local", passwordHash, firstName: "Sys", lastName: "Admin", role: UserRole.SUPERADMIN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, emailVerifiedAt: consentAt, }, }); const kita = await prisma.kita.create({ data: { name: "Waldameisen e.V.", slug: DEMO_KITA_SLUG, notdienstModuleEnabled: true, terminModuleEnabled: true, adressbuchModuleEnabled: true, notdienstMinPerChildPerMonth: 2, notdienstReminderDaysBefore: 7, }, }); const familyMueller = await prisma.family.create({ data: { kitaId: kita.id, name: "Familie Mueller", }, }); const familySchmidtYilmaz = await prisma.family.create({ data: { kitaId: kita.id, name: "Familie Schmidt-Yilmaz", }, }); const familyFischer = await prisma.family.create({ data: { kitaId: kita.id, name: "Familie Fischer", }, }); const admin = await prisma.user.create({ data: { kitaId: kita.id, email: "admin@waldameisen.local", passwordHash, firstName: "Anna", lastName: "Vorstand", role: UserRole.ADMIN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, directoryOptInAt: consentAt, emailVerifiedAt: consentAt, phone: "+49 30 1000 1000", street: "Kitaweg 1", postalCode: "10115", city: "Berlin", }, }); const erzieherUser = await prisma.user.create({ data: { kitaId: kita.id, email: "erzieher@waldameisen.local", passwordHash, firstName: "Eva", lastName: "Erzieherin", role: UserRole.ERZIEHER, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, emailVerifiedAt: consentAt, phone: "+49 30 1000 2000", street: "Kitaweg 1", postalCode: "10115", city: "Berlin", }, }); const koordinator = await prisma.user.create({ data: { kitaId: kita.id, familyId: familyMueller.id, email: "mueller@waldameisen.local", passwordHash, firstName: "Maria", lastName: "Mueller", role: UserRole.KOORDINATOR, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, directoryOptInAt: consentAt, emailVerifiedAt: consentAt, phone: "+49 30 1234 5678", street: "Beispielweg 1", postalCode: "10115", city: "Berlin", }, }); const elternSchmidt = await prisma.user.create({ data: { kitaId: kita.id, familyId: familySchmidtYilmaz.id, email: "schmidt@waldameisen.local", passwordHash, firstName: "Lukas", lastName: "Schmidt", role: UserRole.ELTERN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, emailVerifiedAt: consentAt, }, }); const elternYilmaz = await prisma.user.create({ data: { kitaId: kita.id, familyId: familySchmidtYilmaz.id, email: "yilmaz@waldameisen.local", passwordHash, firstName: "Aylin", lastName: "Yilmaz", role: UserRole.ELTERN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, directoryOptInAt: consentAt, emailVerifiedAt: consentAt, phone: "+49 30 9876 5432", street: "Parkstrasse 8", postalCode: "10405", city: "Berlin", }, }); const pendingParent = await prisma.user.create({ data: { kitaId: kita.id, familyId: familyFischer.id, email: "pending@waldameisen.local", passwordHash: "", firstName: "Lena", lastName: "Fischer", role: UserRole.ELTERN, }, }); const familyKowalski = await prisma.family.create({ data: { kitaId: kita.id, name: "Familie Kowalski", }, }); const familyWeber = await prisma.family.create({ data: { kitaId: kita.id, name: "Familie Weber", }, }); const elternFischer = await prisma.user.create({ data: { kitaId: kita.id, familyId: familyFischer.id, email: "fischer@waldameisen.local", passwordHash, firstName: "Frank", lastName: "Fischer", role: UserRole.ELTERN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, directoryOptInAt: consentAt, emailVerifiedAt: consentAt, phone: "+49 30 5555 1234", street: "Fischerweg 4", postalCode: "10247", city: "Berlin", }, }); const elternKowalski = await prisma.user.create({ data: { kitaId: kita.id, familyId: familyKowalski.id, email: "kowalski@waldameisen.local", passwordHash, firstName: "Jan", lastName: "Kowalski", role: UserRole.ELTERN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, directoryOptInAt: consentAt, emailVerifiedAt: consentAt, phone: "+49 30 5555 5678", street: "Grenzallee 12", postalCode: "12057", city: "Berlin", }, }); const elternWeber = await prisma.user.create({ data: { kitaId: kita.id, familyId: familyWeber.id, email: "weber@waldameisen.local", passwordHash, firstName: "Sabine", lastName: "Weber", role: UserRole.ELTERN, privacyPolicyAcceptedAt: consentAt, privacyPolicyVersion: PRIVACY_POLICY_VERSION, directoryOptInAt: consentAt, emailVerifiedAt: consentAt, phone: "+49 30 5555 9012", street: "Weberstrasse 7", postalCode: "10249", city: "Berlin", }, }); return { kita, familyMueller, familySchmidtYilmaz, familyFischer, familyKowalski, familyWeber, superAdmin, admin, erzieherUser, koordinator, elternSchmidt, elternYilmaz, pendingParent, elternFischer, elternKowalski, elternWeber, }; } async function createChildren({ kita, familyMueller, familySchmidtYilmaz, familyFischer, familyKowalski, familyWeber, }: SeedContext) { const anna = await prisma.child.create({ data: { kitaId: kita.id, familyId: familyMueller.id, firstName: "Anna", lastName: "Mueller", dateOfBirth: new Date("2021-03-15"), }, }); const ben = await prisma.child.create({ data: { kitaId: kita.id, familyId: familyMueller.id, firstName: "Ben", lastName: "Mueller", dateOfBirth: new Date("2023-07-22"), notes: "Geschwisterkind von Anna.", }, }); const clara = await prisma.child.create({ data: { kitaId: kita.id, familyId: familySchmidtYilmaz.id, firstName: "Clara", lastName: "Schmidt", dateOfBirth: new Date("2022-11-03"), }, }); const emil = await prisma.child.create({ data: { kitaId: kita.id, familyId: familySchmidtYilmaz.id, firstName: "Emil", lastName: "Yilmaz", dateOfBirth: new Date("2021-09-09"), }, }); const nina = await prisma.child.create({ data: { kitaId: kita.id, familyId: familyFischer.id, firstName: "Nina", lastName: "Fischer", dateOfBirth: new Date("2022-05-30"), }, }); const leo = await prisma.child.create({ data: { kitaId: kita.id, familyId: familyKowalski.id, firstName: "Leo", lastName: "Kowalski", dateOfBirth: new Date("2022-08-12"), }, }); const mia = await prisma.child.create({ data: { kitaId: kita.id, familyId: familyWeber.id, firstName: "Mia", lastName: "Weber", dateOfBirth: new Date("2021-05-18"), }, }); return { anna, ben, clara, emil, nina, leo, mia }; } async function createEducators(kitaId: string) { const sabine = await prisma.educator.create({ data: { kitaId, firstName: "Sabine", lastName: "Schulze" }, }); const petra = await prisma.educator.create({ data: { kitaId, firstName: "Petra", lastName: "Klein" }, }); const jonas = await prisma.educator.create({ data: { kitaId, firstName: "Jonas", lastName: "Wagner" }, }); const norbert = await prisma.educator.create({ data: { kitaId, firstName: "Norbert", lastName: "Altmann", active: false, }, }); return { sabine, petra, jonas, norbert }; } async function createParentDuties({ kita, koordinator, elternSchmidt, elternYilmaz, }: SeedContext) { const waesche = await prisma.parentDuty.create({ data: { kitaId: kita.id, name: "Waeschedienst", description: "Kita-Waesche waschen und zurueckbringen.", }, }); const einkauf = await prisma.parentDuty.create({ data: { kitaId: kita.id, name: "Einkauf", description: "Woechentlicher Einkauf fuer die Kita-Kueche.", }, }); const garten = await prisma.parentDuty.create({ data: { kitaId: kita.id, name: "Gartendienst", description: "Aussenbereich pflegen und Spielmaterial pruefen.", }, }); await prisma.parentDutyAssignment.createMany({ data: [ { kitaId: kita.id, dutyId: waesche.id, userId: elternSchmidt.id }, { kitaId: kita.id, dutyId: einkauf.id, userId: koordinator.id }, { kitaId: kita.id, dutyId: garten.id, userId: elternYilmaz.id }, ], }); } async function createAbsences( { kita }: SeedContext, children: Awaited>, ) { const today = dateOnly(new Date()); await prisma.absence.createMany({ data: [ { kitaId: kita.id, childId: children.nina.id, startDate: today, endDate: today, reason: AbsenceReason.ILLNESS, note: "Fieber, bleibt heute zuhause.", }, { kitaId: kita.id, childId: children.emil.id, startDate: addDays(today, 1), endDate: addDays(today, 2), reason: AbsenceReason.VACATION, note: "Familienbesuch.", }, ], }); } async function createDutyPlan({ kita, familyMueller, familySchmidtYilmaz, familyFischer, }: SeedContext) { const waesche = await prisma.dutyType.create({ data: { kitaId: kita.id, name: "Waeschedienst", description: "Woechentlicher Dienstplan fuer Kita-Waesche.", }, }); const einkauf = await prisma.dutyType.create({ data: { kitaId: kita.id, name: "Einkauf", description: "Woechentlicher Einkauf nach Kita-Liste.", }, }); const families = [familyMueller, familySchmidtYilmaz, familyFischer]; const currentWeek = startOfIsoWeek(new Date()); await prisma.dutyAssignment.createMany({ data: Array.from({ length: 8 }).flatMap((_, index) => { const startDate = addWeeks(currentWeek, index); const endDate = addDays(startDate, 6); return [ { kitaId: kita.id, dutyTypeId: waesche.id, familyId: families[index % families.length].id, startDate, endDate, }, { kitaId: kita.id, dutyTypeId: einkauf.id, familyId: families[(index + 1) % families.length].id, startDate, endDate, }, ]; }), }); } async function createInvites({ kita, admin, pendingParent }: SeedContext) { const expires = addDays(new Date(), 7); await prisma.invitation.create({ data: { kitaId: kita.id, email: "invite@waldameisen.local", role: UserRole.ELTERN, token: "demo-invite-token", status: InvitationStatus.PENDING, expiresAt: expires, invitedById: admin.id, }, }); await prisma.verificationToken.create({ data: { identifier: pendingParent.id, token: "demo-pending-parent-token", expires, }, }); } async function createAnnouncements(context: SeedContext) { const { kita, admin, koordinator, elternSchmidt, elternYilmaz, elternFischer } = context; const sommerfest = await prisma.announcement.create({ data: { kitaId: kita.id, title: "Sommerfest: Helferliste und Ablauf", content: "## Liebe Familien,\n\nunser Sommerfest findet naechsten Monat im Kita-Garten statt. Bitte merkt euch den Termin vor. Details zu Aufbau, Kuchen und Getraenken folgen ueber das Schwarze Brett.", authorId: admin.id, }, }); const garderobe = await prisma.announcement.create({ data: { kitaId: kita.id, title: "Neue Garderoben-Regelung", content: "Ab Montag bitten wir alle Familien, Wechselkleidung wieder in die beschrifteten Boxen zu legen. So bleibt der Morgen fuer Kinder und Team entspannter.", authorId: admin.id, }, }); const longTitle = await prisma.announcement.create({ data: { kitaId: kita.id, title: "Dies ist eine extrem lange Ankuendigung mit einem superlangen Titel um das Layout des Dashboards und des Schwarzen Bretts auf korrekten Textumbruch und Mobilfreundlichkeit zu testen", content: "Inhalt der superlangen Ankuendigung fuer Layout-Testing.", authorId: admin.id, }, }); const xssTitle = await prisma.announcement.create({ data: { kitaId: kita.id, title: "Boesartiger Titel mit HTML Tags", content: "Dieser Beitrag testet, ob die Anwendung XSS-Angriffe im Titel oder im Inhalt filtert. ", authorId: admin.id, }, }); await prisma.attachment.create({ data: { announcementId: sommerfest.id, fileName: "sommerfest_ablauf.pdf", fileUrl: "https://example.com/sommerfest_ablauf.pdf", fileType: "application/pdf", }, }); await prisma.announcementRead.createMany({ data: [ { userId: koordinator.id, announcementId: sommerfest.id }, { userId: elternSchmidt.id, announcementId: sommerfest.id }, { userId: elternYilmaz.id, announcementId: sommerfest.id }, { userId: elternFischer.id, announcementId: garderobe.id }, ], }); } async function createTermine( context: SeedContext, children: Awaited>, ) { const { kita, admin, koordinator, elternSchmidt, elternYilmaz, elternFischer, elternKowalski } = context; const now = new Date(); const elternabend = await prisma.termin.create({ data: { kitaId: kita.id, title: "Elternabend Fruehling", description: "Austausch zu Terminen, Notdienst und Sommerplanung.", type: TerminType.ELTERNABEND, status: TerminStatus.CONFIRMED, startDate: addDays(now, 10), endDate: addDays(now, 10), allDay: false, mitbringselListEnabled: true, createdById: admin.id, approvedById: admin.id, approvedAt: now, }, }); await prisma.termin.create({ data: { kitaId: kita.id, title: "Teamtag", description: "Die Kita bleibt wegen interner Fortbildung geschlossen.", type: TerminType.TEAMTAG, status: TerminStatus.CONFIRMED, startDate: addDays(now, 18), endDate: addDays(now, 18), allDay: true, createdById: admin.id, approvedById: admin.id, approvedAt: now, }, }); await prisma.termin.create({ data: { kitaId: kita.id, title: "Elterncafe im Garten", description: "Anfrage eines Elternteils, noch nicht bestaetigt.", type: TerminType.ELTERNCAFE, status: TerminStatus.PENDING, startDate: addDays(now, 24), endDate: addDays(now, 24), allDay: false, createdById: elternSchmidt.id, }, }); await prisma.termin.create({ data: { kitaId: kita.id, title: "Spontaner Ausflug", description: "Beispiel fuer eine abgelehnte Terminanfrage.", type: TerminType.SONSTIGES, status: TerminStatus.REJECTED, startDate: addDays(now, -6), endDate: addDays(now, -6), allDay: true, createdById: elternYilmaz.id, approvedById: koordinator.id, approvedAt: addDays(now, -5), rejectionReason: "Zu kurzfristig fuer die Gruppe.", }, }); const zooAusflug = await prisma.termin.create({ data: { kitaId: kita.id, title: "Ausflug in den Zoo", description: "Gemeinsamer Ausflug der gesamten Kita-Gruppe in den Berliner Zoo.", type: TerminType.SONSTIGES, status: TerminStatus.CONFIRMED, startDate: addDays(now, 15), endDate: addDays(now, 15), allDay: true, requiresRsvp: true, rsvpDeadline: addDays(now, 5), participantInfo: "Bitte wetterfeste Kleidung und Rucksack-Verpflegung mitbringen.", createdById: admin.id, approvedById: admin.id, approvedAt: now, }, }); await prisma.eventParticipation.createMany({ data: [ { kitaId: kita.id, eventId: zooAusflug.id, childId: children.anna.id, status: EventParticipationStatus.ATTENDING }, { kitaId: kita.id, eventId: zooAusflug.id, childId: children.clara.id, status: EventParticipationStatus.ATTENDING }, { kitaId: kita.id, eventId: zooAusflug.id, childId: children.emil.id, status: EventParticipationStatus.NOT_ATTENDING }, { kitaId: kita.id, eventId: zooAusflug.id, childId: children.leo.id, status: EventParticipationStatus.ATTENDING }, ], }); await prisma.termin.create({ data: { kitaId: kita.id, title: "Mitmach-Tag (Garten)", description: "Gemeinsames Herrichten des Gartens fuer die warme Jahreszeit. (Abgesagt wegen Dauerregen)", type: TerminType.MITMACH_TAG, status: TerminStatus.CANCELLED, startDate: addDays(now, 2), endDate: addDays(now, 2), allDay: false, createdById: admin.id, }, }); const sommerfestEvent = await prisma.termin.create({ data: { kitaId: kita.id, title: "Sommerfest Waldameisen", description: "Unser jaehrliches grosses Sommerfest im Kita-Garten mit Buffet und Spielstationen.", type: TerminType.KITA_FEST, status: TerminStatus.CONFIRMED, startDate: addDays(now, 30), endDate: addDays(now, 30), allDay: false, mitbringselListEnabled: true, createdById: admin.id, approvedById: admin.id, approvedAt: now, }, }); const highDensityDay = addDays(now, 7); for (let i = 1; i <= 8; i++) { await prisma.termin.create({ data: { kitaId: kita.id, title: `Parallel-Termin #${i}`, description: `Beschreibung fuer Parallel-Termin #${i}`, type: TerminType.SONSTIGES, status: TerminStatus.CONFIRMED, startDate: highDensityDay, endDate: highDensityDay, allDay: false, createdById: admin.id, approvedById: admin.id, approvedAt: now, }, }); } await prisma.termin.create({ data: { kitaId: kita.id, title: "Boesartiger Termin ", description: "XSS Test im Terminkalender ", type: TerminType.SONSTIGES, status: TerminStatus.CONFIRMED, startDate: addDays(now, 5), endDate: addDays(now, 5), allDay: true, createdById: admin.id, approvedById: admin.id, approvedAt: now, }, }); await prisma.mitbringselItem.createMany({ data: [ { kitaId: kita.id, terminId: elternabend.id, userId: koordinator.id, content: "Obstplatte", }, { kitaId: kita.id, terminId: elternabend.id, userId: elternSchmidt.id, content: "Brot und Aufstrich", }, { kitaId: kita.id, terminId: sommerfestEvent.id, userId: elternYilmaz.id, content: "Nudelsalat (vegan)", }, { kitaId: kita.id, terminId: sommerfestEvent.id, userId: elternFischer.id, content: "Mehrweg-Becher (ca. 30 Stk.)", }, { kitaId: kita.id, terminId: sommerfestEvent.id, userId: elternKowalski.id, content: "Apfelsaft & Wasser", }, ], }); } async function createNotdienstData( context: SeedContext, children: Awaited>, educators: Awaited>, ) { const { kita, koordinator, elternSchmidt, elternYilmaz, elternWeber, elternKowalski } = context; const today = dateOnly(new Date()); const currentMonth = startOfMonth(today); const previousMonth = getPreviousMonth(today); const previousAssignmentDate = getLastDayOfPreviousMonth(today); const targetMonth = getTargetMonth(today); const availabilityPairs = [ { childId: children.anna.id, userId: koordinator.id }, { childId: children.clara.id, userId: elternSchmidt.id }, { childId: children.emil.id, userId: elternYilmaz.id }, { childId: children.ben.id, userId: koordinator.id }, { childId: children.leo.id, userId: elternKowalski.id }, { childId: children.mia.id, userId: elternWeber.id }, ]; const targetWorkingDays = getWorkingDays( targetMonth.getFullYear(), targetMonth.getMonth() + 1, 12, ); await prisma.notdienstAvailability.createMany({ data: targetWorkingDays.map((date, index) => { const pair = availabilityPairs[index % availabilityPairs.length]; return { kitaId: kita.id, childId: pair.childId, userId: pair.userId, date, }; }), }); const currentPlan = await prisma.notdienstPlan.create({ data: { kitaId: kita.id, year: currentMonth.getFullYear(), month: currentMonth.getMonth() + 1, status: NotdienstPlanStatus.PUBLISHED, createdById: koordinator.id, publishedAt: new Date(), }, }); await prisma.notdienstAssignment.create({ data: { kitaId: kita.id, planId: currentPlan.id, childId: children.anna.id, date: today, }, }); const alertAssignmentDate = addDays(today, 3); const pendingAlertAssignment = await prisma.notdienstAssignment.create({ data: { kitaId: kita.id, planId: currentPlan.id, childId: children.emil.id, date: alertAssignmentDate, }, }); await prisma.notdienstAlert.create({ data: { kitaId: kita.id, assignmentId: pendingAlertAssignment.id, parentUserId: elternYilmaz.id, triggeredById: koordinator.id, educatorId: educators.petra.id, status: NotdienstAlertStatus.PENDING, confirmationToken: "demo-pending-alert-token", triggeredAt: today, notes: "Es fehlen zwei Kraefte. Bitte Notdienst besetzen.", }, }); const previousPlan = await prisma.notdienstPlan.create({ data: { kitaId: kita.id, year: previousMonth.getFullYear(), month: previousMonth.getMonth() + 1, status: NotdienstPlanStatus.PUBLISHED, createdById: koordinator.id, publishedAt: addDays(today, -10), }, }); const pastAssignment = await prisma.notdienstAssignment.create({ data: { kitaId: kita.id, planId: previousPlan.id, childId: children.clara.id, date: previousAssignmentDate, }, }); await prisma.notdienstAlert.create({ data: { kitaId: kita.id, assignmentId: pastAssignment.id, parentUserId: elternSchmidt.id, triggeredById: koordinator.id, educatorId: educators.sabine.id, status: NotdienstAlertStatus.CONFIRMED, confirmationToken: "demo-confirmed-alert-token", triggeredAt: previousAssignmentDate, confirmedAt: previousAssignmentDate, notes: "Beispiel-Alarm aus Seed-Daten.", }, }); const draftPlan = await prisma.notdienstPlan.create({ data: { kitaId: kita.id, year: targetMonth.getFullYear(), month: targetMonth.getMonth() + 1, status: NotdienstPlanStatus.DRAFT, createdById: koordinator.id, }, }); } async function createAuditLogs({ kita, superAdmin, admin }: SeedContext) { await prisma.auditLog.createMany({ data: [ { actorUserId: superAdmin.id, actorEmail: superAdmin.email, action: "KITA_CREATE", targetType: AuditTargetType.KITA, targetId: kita.id, targetLabel: kita.name, targetKitaId: kita.id, metadata: { name: kita.name, slug: kita.slug }, createdAt: addDays(new Date(), -30), }, { actorUserId: superAdmin.id, actorEmail: superAdmin.email, action: "USER_ROLE_UPDATE", targetType: AuditTargetType.USER, targetId: admin.id, targetLabel: admin.email, targetKitaId: kita.id, metadata: { oldRole: "ELTERN", newRole: "ADMIN" }, createdAt: addDays(new Date(), -28), }, { actorUserId: admin.id, actorEmail: admin.email, action: "KITA_MODULE_UPDATE", targetType: AuditTargetType.KITA, targetId: kita.id, targetLabel: kita.name, targetKitaId: kita.id, metadata: { notdienstModuleEnabled: true, terminModuleEnabled: true, adressbuchModuleEnabled: true, }, createdAt: addDays(new Date(), -25), }, ], }); } async function createDemoData() { console.log("Seeding Kita-Planer demo data..."); const passwordHash = await hash(DEFAULT_PASSWORD, 12); const consentAt = new Date(); const context = await createDemoUsers(passwordHash, consentAt); const children = await createChildren(context); const educators = await createEducators(context.kita.id); await createParentDuties(context); await createDutyPlan(context); await createAbsences(context, children); await createInvites(context); await createAnnouncements(context); await createTermine(context, children); await createNotdienstData(context, children, educators); await createAuditLogs(context); printSummary(context, children); } async function seedIfEmpty() { if (!(await isDatabaseEmpty())) { console.log("Seed uebersprungen: Datenbank enthaelt bereits Kita- oder User-Daten."); return; } await createDemoData(); } function printSummary( context: SeedContext, children: Awaited>, ) { const { kita, superAdmin, admin, erzieherUser, koordinator, elternSchmidt, elternYilmaz, elternFischer, elternKowalski, elternWeber, pendingParent, } = context; console.log("Seed complete"); console.log(""); console.log(` Kita: ${kita.name} (${kita.slug})`); console.log(""); console.log(` Logins (Passwort jeweils: ${DEFAULT_PASSWORD})`); console.log(` Superadmin: ${superAdmin.email}`); console.log(` Admin: ${admin.email}`); console.log(` Erzieherin: ${erzieherUser.email}`); console.log(` Koordinator: ${koordinator.email}`); console.log(` Eltern: ${elternSchmidt.email}`); console.log(` Eltern: ${elternYilmaz.email}`); console.log(` Eltern (Fischer): ${elternFischer.email}`); console.log(` Eltern (Kowalski): ${elternKowalski.email}`); console.log(` Eltern (Weber): ${elternWeber.email}`); console.log(""); console.log( ` Offener Invite: ${pendingParent.email} -> /invite/demo-pending-parent-token`, ); console.log( ` Kinder: ${Object.values(children) .map((child) => `${child.firstName} ${child.lastName}`) .join(", ")}`, ); } async function main() { if (!isSeedAllowed()) { return; } if (RESET_MODE) { console.log("Reset demo seed data..."); await resetDemoData(); await createDemoData(); return; } await seedIfEmpty(); } main() .catch((error) => { console.error("Seed failed:", error); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });