154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { UserRole } from "@prisma/client";
|
|
import { z } from "zod";
|
|
|
|
import { requireRole } from "@/lib/auth-utils";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { getTargetMonthData } from "@/lib/date-utils";
|
|
|
|
// =====================================================================
|
|
// /dashboard/notdienst · Server Actions
|
|
// ---------------------------------------------------------------------
|
|
// Speichert die gebuchten Termine der Eltern im Zielmonat.
|
|
// Die Termine werden Round-Robin auf die Kinder aufgeteilt.
|
|
// =====================================================================
|
|
|
|
const dateKeyPattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
|
const payloadSchema = z.object({
|
|
childrenIds: z.array(z.string().min(1).max(64)).min(1).max(20),
|
|
dates: z.array(
|
|
z.string().regex(dateKeyPattern).refine((value) => {
|
|
const date = dateKeyToUtcDate(value);
|
|
return Number.isFinite(date.getTime()) && value === date.toISOString().slice(0, 10);
|
|
}),
|
|
).max(31),
|
|
});
|
|
|
|
function dateKeyToUtcDate(dateKey: string) {
|
|
const [year, month, day] = dateKey.split("-").map(Number);
|
|
return new Date(Date.UTC(year, month - 1, day));
|
|
}
|
|
|
|
export async function saveNotdienstAvailabilities(payloadRaw: {
|
|
childrenIds: string[];
|
|
dates: string[];
|
|
}) {
|
|
const session = await requireRole([
|
|
UserRole.ELTERN,
|
|
UserRole.ADMIN,
|
|
UserRole.KOORDINATOR,
|
|
]);
|
|
|
|
const parsed = payloadSchema.safeParse(payloadRaw);
|
|
if (!parsed.success) {
|
|
return { error: "Ungültige Eingabedaten." };
|
|
}
|
|
|
|
const { childrenIds } = parsed.data;
|
|
const dates = Array.from(new Set(parsed.data.dates)).sort();
|
|
|
|
// ── 1. Sperrfrist prüfen ───────────────────────────────────────────
|
|
const { targetYear, targetMonth, isLocked } = getTargetMonthData();
|
|
if (isLocked) {
|
|
return { error: "Die Planung für diesen Monat ist bereits abgeschlossen." };
|
|
}
|
|
|
|
const kitaId = session.user.kitaId;
|
|
if (!kitaId) {
|
|
return { error: "Kein Mandant zugeordnet." };
|
|
}
|
|
if (!session.user.familyId) {
|
|
return { error: "Dein Account ist noch keinem Haushalt zugeordnet." };
|
|
}
|
|
|
|
// ── 2. Validierung (Soll-Tage) ─────────────────────────────────────
|
|
const kita = await prisma.kita.findUniqueOrThrow({
|
|
where: { id: kitaId },
|
|
select: { notdienstMinPerChildPerMonth: true },
|
|
});
|
|
|
|
const requiredTotal = childrenIds.length * kita.notdienstMinPerChildPerMonth;
|
|
if (dates.length < requiredTotal) {
|
|
return {
|
|
error: `Bitte wähle mindestens ${requiredTotal} Tage aus.`,
|
|
};
|
|
}
|
|
|
|
// Sicherstellen, dass alle Kinder zum Haushalt des angemeldeten Users gehören.
|
|
const validChildren = await prisma.child.findMany({
|
|
where: {
|
|
id: { in: childrenIds },
|
|
kitaId,
|
|
familyId: session.user.familyId,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
|
|
if (validChildren.length !== childrenIds.length) {
|
|
return { error: "Ungültige Kinder-Zuordnung erkannt." };
|
|
}
|
|
|
|
// Termine in Date-Objekte wandeln und sortieren
|
|
const parsedDates = dates.map(dateKeyToUtcDate);
|
|
const hasOutsideTargetMonth = parsedDates.some(
|
|
(date) =>
|
|
date.getUTCFullYear() !== targetYear ||
|
|
date.getUTCMonth() !== targetMonth - 1,
|
|
);
|
|
if (hasOutsideTargetMonth) {
|
|
return { error: "Bitte wähle Termine im Zielmonat aus." };
|
|
}
|
|
|
|
// Round-Robin Zuweisung
|
|
const inserts: {
|
|
kitaId: string;
|
|
childId: string;
|
|
userId: string;
|
|
date: Date;
|
|
}[] = [];
|
|
|
|
for (let i = 0; i < parsedDates.length; i++) {
|
|
const childId = validChildren[i % validChildren.length].id;
|
|
inserts.push({
|
|
kitaId,
|
|
childId,
|
|
userId: session.user.id,
|
|
date: parsedDates[i],
|
|
});
|
|
}
|
|
|
|
// ── 3. Datenbank-Transaktion ───────────────────────────────────────
|
|
try {
|
|
await prisma.$transaction(async (tx) => {
|
|
// a) Alle bestehenden Haushaltseinträge im Zielmonat für diese Kinder löschen
|
|
await tx.notdienstAvailability.deleteMany({
|
|
where: {
|
|
kitaId,
|
|
childId: { in: childrenIds },
|
|
date: {
|
|
gte: new Date(Date.UTC(targetYear, targetMonth - 1, 1)),
|
|
lt: new Date(Date.UTC(targetYear, targetMonth, 1)),
|
|
},
|
|
},
|
|
});
|
|
|
|
// b) Neue Einträge setzen
|
|
if (inserts.length > 0) {
|
|
await tx.notdienstAvailability.createMany({
|
|
data: inserts,
|
|
skipDuplicates: true,
|
|
});
|
|
}
|
|
});
|
|
|
|
revalidatePath("/dashboard/notdienst");
|
|
return { success: true, dates };
|
|
} catch (error) {
|
|
console.error("Fehler beim Speichern der Notdienste:", error);
|
|
return { error: "Ein Fehler ist aufgetreten beim Speichern der Termine." };
|
|
}
|
|
}
|