Files
KIBU/scripts/seed-demo.ts
T
Paul Schoeneck 75af38a4b6 Add licensing information and update README for KIBU
- Introduced additional terms under Section 7 of the GNU AGPL v3 for KIBU, including attribution, marking of modified versions, and trademark usage.
- Added MIT License file for clarity on licensing options.
- Updated README.md to reflect the new licensing terms, including detailed usage rights and obligations.
- Created REUSE.toml for SPDX compliance and license annotations.
- Added THIRD_PARTY_NOTICES.md to clarify third-party dependencies and their licenses.
- Updated various source files and scripts to include SPDX headers for copyright and licensing.
- Enhanced footer in layout to display licensing information and project link.
- Implemented a new license page to present the AGPL and additional terms clearly.
2026-11-03 08:27:12 +01:00

112 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-FileCopyrightText: 2026 Paul Schoeneck
//
// SPDX-License-Identifier: AGPL-3.0-only AND LicenseRef-KIBU-Additional-Terms
import "dotenv/config";
import { db } from "../src/lib/db";
import { auditContext } from "../src/lib/audit";
import { localClock, localInstant } from "../src/lib/time";
import { getSlots } from "../src/lib/slots";
const demoChildren = [
["Mila", "Sonnental", "1a"], ["Noah", "Wiesenbach", "2b"], ["Emma", "Blütenfeld", "3a"],
["Finn", "Wolkenstein", "1b"], ["Lina", "Sommerhain", "2a"], ["Ben", "Sternwald", "4a"],
["Hannah", "Lindenau", "3b"], ["Leon", "Regenbogen", "1a"], ["Clara", "Morgenrot", "2b"],
["Elias", "Birkenfels", "4b"], ["Amelie", "Fuchswiese", "3a"], ["Oskar", "Kleebach", "2a"],
];
const clockTime = (minutes: number) => {
const clamped = Math.max(1, Math.min(1439, minutes));
return `${Math.floor(clamped / 60).toString().padStart(2, "0")}:${(clamped % 60).toString().padStart(2, "0")}`;
};
async function seed() {
const staff = await db.query<{ id: string }>("SELECT id FROM staff ORDER BY created_at, id LIMIT 1");
if (!staff.rowCount) throw new Error("Zuerst npm run db:setup ausführen.");
const client = await db.connect();
const now = new Date();
const clock = localClock(now);
const timeZone = process.env.APP_TIMEZONE ?? "Europe/Berlin";
const slots = (await getSlots()).filter((slot) => slot.active);
const currentMinute = Number(clock.time.slice(0, 2)) * 60 + Number(clock.time.slice(3, 5));
let inserted = 0;
try {
await client.query("BEGIN");
await auditContext(client, staff.rows[0].id, "Fiktive Demodaten zum Testen – keine realen Personen.");
for (const [index, [firstName, lastName, className]] of demoChildren.entries()) {
const id = `d3000000-0000-4000-8000-${(index + 1).toString().padStart(12, "0")}`;
const result = await client.query(`INSERT INTO children(id, first_name, last_name, class_name)
VALUES ($1, $2, $3, $4) ON CONFLICT(id) DO NOTHING RETURNING id`, [id, firstName, lastName, className]);
if (!result.rowCount) continue;
inserted++;
const weekdays = [...new Set([1, 2, 3, 4, 5, clock.weekday])];
for (const weekday of weekdays) {
const slot = slots.length ? slots[(index + weekday) % slots.length] : undefined;
await client.query(`INSERT INTO weekly_schedules(child_id, weekday, arrival, pickup, pickup_end, pickup_slot_id)
VALUES ($1, $2, $3, $4, $5, $6)`,
[id, weekday, weekday % 2 ? "12:00" : "12:30", slot?.start ?? "16:00", slot?.end ?? null, slot?.id ?? null]);
}
await client.query(`INSERT INTO pickup_contacts(child_id, name, relationship, authorized, notes) VALUES
($1, $2, 'Mutter', true, 'Fiktiver Demokontakt. Ausweis bei unbekannter Person prüfen.'),
($1, $3, 'Vater', true, 'Fiktiver Demokontakt.')`,
[id, `Alex ${lastName}`, `Robin ${lastName}`]);
if (index % 3 === 0) {
await client.query(`INSERT INTO pickup_contacts(child_id, name, relationship, authorized, notes)
VALUES ($1, $2, 'Bekannte Person', false, 'Demobeispiel: keine aktuelle Abholberechtigung.')`, [id, `Kim ${lastName}`]);
}
if ([2, 5, 9].includes(index)) {
for (const weekday of weekdays) {
await client.query("INSERT INTO alone_rules(child_id, weekday, earliest) VALUES ($1, $2, $3)",
[id, weekday, weekday === clock.weekday ? clockTime(currentMinute - 10) : "15:00"]);
}
}
let historyIndex = 0;
for (let offset = 1; offset <= 14; offset++) {
const date = new Date(`${clock.day}T12:00:00Z`);
date.setUTCDate(date.getUTCDate() - offset);
if ([0, 6].includes(date.getUTCDay())) continue;
const day = date.toISOString().slice(0, 10);
const sick = (index + offset) % 11 === 0;
const forgotten = index === 0 && historyIndex === 0;
historyIndex++;
const arrival = sick ? null : localInstant(day, "12:15", timeZone);
const pickup = sick || forgotten ? null : localInstant(day, clockTime(14 * 60 + (index % 5) * 30), timeZone);
await client.query(`INSERT INTO attendance(child_id, day, status, arrived_at, picked_up_at, pickup_kind, updated_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, day,
sick ? "sick" : forgotten ? "arrived" : "picked_up", arrival, pickup,
pickup ? index % 4 === 0 ? "parents" : "planned" : null, staff.rows[0].id]);
}
const plannedPickup = clockTime(currentMinute + (index === 0 ? -10 : index === 1 || index === 2 ? 10 : 120 + index * 10));
const arrivalTime = clockTime(currentMinute - 90);
if (index === 8) {
await client.query("INSERT INTO day_overrides(child_id, day, attending) VALUES ($1, $2, false)", [id, clock.day]);
} else {
await client.query(`INSERT INTO day_overrides(child_id, day, attending, arrival, pickup)
VALUES ($1, $2, true, $3, $4)`, [id, clock.day, "00:00", plannedPickup]);
}
if ([7, 8, 10].includes(index) || currentMinute < 3) continue;
const sick = index === 4;
const picked = index === 5 || index === 6;
await client.query(`INSERT INTO attendance(child_id, day, status, arrived_at, picked_up_at, pickup_kind, updated_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, clock.day, sick ? "sick" : picked ? "picked_up" : "arrived",
sick ? null : localInstant(clock.day, arrivalTime, timeZone),
picked ? localInstant(clock.day, clockTime(currentMinute - 5), timeZone) : null,
picked ? index === 5 ? "parents" : "planned" : null, staff.rows[0].id]);
}
await client.query("COMMIT");
console.log(`${inserted} fiktive Kinder angelegt (${demoChildren.length - inserted} bereits vorhanden, unverändert).`);
console.log("Mit Wochenplänen, Tagesabweichungen, Abholberechtigten und zwei Wochen Verlauf.");
console.log("Mila Sonnental hat im letzten Betreuungstag eine vergessene Abholung zum Korrigieren.");
console.log("Passwörter und bestehende Datensätze wurden nicht verändert.");
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
seed().catch((error: unknown) => {
console.error("Demodaten konnten nicht angelegt werden:", error);
process.exitCode = 1;
}).finally(() => db.end());