Files
kita-planer/prisma/seed.ts
T

1168 lines
31 KiB
TypeScript

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<ReturnType<typeof createDemoUsers>>;
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<ReturnType<typeof createChildren>>,
) {
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 <script>alert('XSS_NEWS_TITLE')</script> HTML Tags",
content: "Dieser Beitrag testet, ob die Anwendung XSS-Angriffe im Titel oder im Inhalt filtert. <script>alert('XSS_NEWS_CONTENT')</script>",
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<ReturnType<typeof createChildren>>,
) {
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 <script>alert('XSS_CAL_TITLE')</script>",
description: "XSS Test im Terminkalender <script>alert('XSS_CAL_DESC')</script>",
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<ReturnType<typeof createChildren>>,
educators: Awaited<ReturnType<typeof createEducators>>,
) {
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<ReturnType<typeof createChildren>>,
) {
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();
});