Files
kita-planer/src/app/dashboard/notdienst/actions.ts
T

126 lines
4.7 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 { formatUtcDateKey, getTargetMonthData } from "@/lib/date-utils";
import { serializableTransaction } from "@/lib/transaction";
import { UserFacingError, publicActionError } from "@/lib/action-error";
import { loadNotdienstDays } from "@/lib/notdienst-planning";
// =====================================================================
// /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." };
}
try {
await serializableTransaction(async (tx) => {
const [kita, children, workingDays] = await Promise.all([
tx.kita.findUniqueOrThrow({
where: { id: kitaId },
select: { notdienstMinPerChildPerMonth: true },
}),
tx.child.findMany({
where: { kitaId, familyId: session.user.familyId!, active: true },
select: { id: true },
orderBy: { id: "asc" },
}),
loadNotdienstDays(tx, kitaId, targetYear, targetMonth),
]);
const suppliedIds = new Set(childrenIds);
if (!children.length || suppliedIds.size !== childrenIds.length ||
suppliedIds.size !== children.length || !children.every((child) => suppliedIds.has(child.id))) {
throw new UserFacingError("Bitte lade die Seite neu und wähle die Termine für alle aktiven Kinder deines Haushalts aus.");
}
const validDates = new Set(workingDays.map(formatUtcDateKey));
if (dates.some((date) => !validDates.has(date))) {
throw new UserFacingError("Bitte wähle nur geöffnete Werktage im Zielmonat aus. Wochenenden und Schließtage sind nicht möglich.");
}
const requiredTotal = children.length * kita.notdienstMinPerChildPerMonth;
if (dates.length < requiredTotal) {
throw new UserFacingError(`Bitte wähle mindestens ${requiredTotal} Tage aus.`);
}
const activeIds = children.map((child) => child.id);
await tx.notdienstAvailability.deleteMany({
where: {
kitaId,
child: { kitaId, familyId: session.user.familyId! },
date: {
gte: new Date(Date.UTC(targetYear, targetMonth - 1, 1)),
lt: new Date(Date.UTC(targetYear, targetMonth, 1)),
},
},
});
await tx.notdienstAvailability.createMany({
data: dates.map((date, index) => ({
kitaId,
childId: activeIds[index % activeIds.length],
userId: session.user.id,
date: dateKeyToUtcDate(date),
})),
});
});
revalidatePath("/dashboard/notdienst");
revalidatePath("/dashboard/notdienst/plan");
revalidatePath("/dashboard");
return { success: true, dates };
} catch (error) {
if (!(error instanceof UserFacingError)) console.error("Fehler beim Speichern der Notdienste:", error);
return { error: publicActionError(error, "Ein Fehler ist aufgetreten beim Speichern der Termine.") };
}
}