93 lines
7.0 KiB
TypeScript
93 lines
7.0 KiB
TypeScript
/** Synthetic fixture for public product screenshots. Never run against real data. */
|
|
import { PrismaClient, UserRole, TerminType, type User, type Child } from "@prisma/client";
|
|
import { hash } from "bcryptjs";
|
|
|
|
const url = new URL(process.env.DATABASE_URL ?? "postgresql://invalid");
|
|
if (!['localhost', '127.0.0.1'].includes(url.hostname) ||
|
|
url.pathname !== '/kita_screenshots_test') {
|
|
throw new Error('Screenshot fixtures require the isolated local kita_screenshots_test database.');
|
|
}
|
|
const prisma = new PrismaClient();
|
|
const date = (value: string) => new Date(`${value}T00:00:00Z`);
|
|
|
|
async function main() {
|
|
if (await prisma.kita.count() || await prisma.user.count()) {
|
|
throw new Error('Database must be empty; existing data will never be reset.');
|
|
}
|
|
const passwordHash = await hash('demo-screenshots-only', 10);
|
|
await prisma.$transaction(async (tx) => {
|
|
const kita = await tx.kita.create({ data: { name: 'Kita Sonnenwinkel', slug: 'sonnenwinkel-demo' } });
|
|
const kitaId = kita.id;
|
|
const common = { kitaId, passwordHash, emailVerifiedAt: date('2026-09-01'),
|
|
privacyPolicyAcceptedAt: date('2026-09-01'), privacyPolicyVersion: '2026-05-01' };
|
|
const admin = await tx.user.create({ data: { ...common, email: 'admin@example.invalid', firstName: 'Mara', lastName: 'Winter', role: UserRole.ADMIN } });
|
|
const households = [
|
|
['Berger', 'Lena', 'Jonas', 'Emma', 'Leo'],
|
|
['Hoffmann', 'Anna', 'Tim', 'Mila'],
|
|
['Schneider', 'Clara', 'Ben', 'Paul'],
|
|
['Neumann', 'Nora', 'Felix', 'Lotta'],
|
|
['Weber', 'Sarah', 'David', 'Finn'],
|
|
['Koch', 'Julia', 'Max', 'Ella'],
|
|
];
|
|
const parents: User[] = [];
|
|
const children: Child[] = [];
|
|
for (const [lastName, firstName, partner, ...childNames] of households) {
|
|
const family = await tx.family.create({ data: { kitaId, name: `Familie ${lastName}` } });
|
|
for (const name of [firstName, partner]) {
|
|
parents.push(await tx.user.create({ data: { ...common, familyId: family.id, firstName: name, lastName,
|
|
email: `${name.toLowerCase()}@example.invalid`, role: UserRole.ELTERN,
|
|
directoryOptInAt: date('2026-09-01'), city: 'Musterstadt' } }));
|
|
}
|
|
for (const name of childNames) {
|
|
children.push(await tx.child.create({ data: { kitaId, familyId: family.id, firstName: name, lastName } }));
|
|
}
|
|
}
|
|
await tx.notdienstAvailability.createMany({ data: [2, 4, 6, 10, 12, 17, 20, 25].map(day => ({
|
|
kitaId, childId: children[0].id, userId: parents[0].id,
|
|
date: date(`2026-11-${String(day).padStart(2, '0')}`),
|
|
})) });
|
|
await tx.absence.createMany({ data: [
|
|
{ kitaId, childId: children[0].id, startDate: date('2026-10-12'), endDate: date('2026-10-14'), reason: 'ILLNESS' },
|
|
{ kitaId, childId: children[1].id, startDate: date('2026-10-26'), endDate: date('2026-10-30'), reason: 'VACATION', note: 'Familienurlaub in den Herbstferien' },
|
|
] });
|
|
const events: [string, TerminType, string, string, boolean][] = [
|
|
['Elterncafé', 'ELTERNCAFE', '2026-10-08T13:00:00Z', '2026-10-08T14:30:00Z', false],
|
|
['Ausflug in den Stadtwald', 'SONSTIGES', '2026-10-15T07:00:00Z', '2026-10-15T11:00:00Z', false],
|
|
['Elternabend', 'ELTERNABEND', '2026-10-20T16:00:00Z', '2026-10-20T18:00:00Z', false],
|
|
['Herbstfest im Garten', 'KITA_FEST', '2026-10-23T13:00:00Z', '2026-10-23T16:00:00Z', false],
|
|
['Teamtag · Kita geschlossen', 'TEAMTAG', '2026-10-30T00:00:00Z', '2026-10-30T23:59:00Z', true],
|
|
['Elterncafé', 'ELTERNCAFE', '2026-11-05T14:00:00Z', '2026-11-05T15:30:00Z', false],
|
|
['Laternenfest', 'KITA_FEST', '2026-11-11T15:00:00Z', '2026-11-11T17:00:00Z', false],
|
|
['Gartenaktion', 'MITMACH_TAG', '2026-11-14T09:00:00Z', '2026-11-14T12:00:00Z', false],
|
|
['Teamtag · Kita geschlossen', 'TEAMTAG', '2026-11-23T00:00:00Z', '2026-11-23T23:59:00Z', true],
|
|
];
|
|
for (const [title, type, start, end, allDay] of events) {
|
|
const event = await tx.termin.create({ data: { kitaId, title, type, status: 'CONFIRMED', startDate: new Date(start), endDate: new Date(end), allDay,
|
|
description: title.includes('Herbstfest') ? 'Wir feiern gemeinsam mit allen Familien. Für Snacks und Getränke gibt es eine Mitbringliste.' : 'Gemeinsam planen wir unseren Kita-Alltag.',
|
|
createdById: admin.id, approvedById: admin.id, approvedAt: date('2026-10-01'),
|
|
requiresRsvp: type === 'KITA_FEST', rsvpMode: 'FAMILY', mitbringselListEnabled: type === 'KITA_FEST' } });
|
|
if (type === 'KITA_FEST') {
|
|
await tx.familyEventParticipation.create({ data: { kitaId, eventId: event.id, familyId: parents[0].familyId!, status: 'ATTENDING', adultCount: 2 } });
|
|
await tx.mitbringselItem.createMany({ data: parents.slice(0, 3).map((parent, index) => ({ kitaId, terminId: event.id, userId: parent.id, content: ['Apfelkuchen', 'Gemüsesticks', 'Eine Kanne Tee'][index] })) });
|
|
}
|
|
}
|
|
for (const [title, content, created, updated] of [
|
|
['Herbstfest: Wir freuen uns auf euch!', 'Am **Freitag, 23. Oktober**, feiern wir ab 15 Uhr im Kita-Garten.\n\nBitte tragt euch im Kalender für die Teilnahme und in die Mitbringliste ein. Gemeinsam sorgen wir für ein buntes Buffet.\n\n**Aktualisierung:** Bei Regen feiern wir im Gruppenraum.', '2026-10-09', '2026-10-13'],
|
|
['Elternabend am 20. Oktober', 'Wir treffen uns um **18 Uhr** im großen Gruppenraum.\n\nAuf der Agenda stehen der Kita-Alltag, die nächsten Ausflüge und eure Fragen. Wir freuen uns auf den Austausch!', '2026-10-08', '2026-10-08'],
|
|
['Gemeinsam unseren Garten vorbereiten', 'Am 14. November machen wir unseren Garten winterfest. Kleine und große Helfer sind willkommen!\n\nHandschuhe und gute Laune reichen. Alle Details findet ihr im Kalender.', '2026-10-06', '2026-10-06'],
|
|
]) {
|
|
await tx.announcement.create({ data: { kitaId, authorId: admin.id, title, content, audience: 'ALLE',
|
|
createdAt: date(created), updatedAt: date(updated), revision: created === updated ? 1 : 2, editedAt: created === updated ? null : date(updated) } });
|
|
}
|
|
const staff = await tx.user.create({ data: { ...common, email: 'team@example.invalid', firstName: 'Sophie', lastName: 'Lind', role: UserRole.ERZIEHER } });
|
|
const educator = await tx.educator.create({ data: { kitaId, firstName: staff.firstName, lastName: staff.lastName } });
|
|
const profile = await tx.staffProfile.create({ data: { kitaId, educatorId: educator.id, userId: staff.id,
|
|
employmentStart: date('2026-01-01'), trackingStart: date('2026-10-01'), openingMinutes: 120,
|
|
workPatterns: { create: { effectiveFrom: date('2026-01-01'), dailyMinutes: [420,420,420,420,420,0,0] } },
|
|
leaveAccounts: { create: { year: 2026, entitlementUnits: 60, carryUnits: 0 } } } });
|
|
await tx.staffTimeEntry.createMany({ data: [5,6,7,8,9].map(day => ({ profileId: profile.id, date: date(`2026-10-${String(day).padStart(2,'0')}`), startMinute: 480, endMinute: 930, breakMinutes: 30 })) });
|
|
}, { timeout: 30000 });
|
|
console.log('Synthetic screenshot data ready. Logins: lena@example.invalid / team@example.invalid. Password: demo-screenshots-only.');
|
|
}
|
|
main().finally(() => prisma.$disconnect());
|