feat: complete notdienst planning, quick wins and notifications
This commit is contained in:
@@ -5,8 +5,10 @@ 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";
|
||||
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
|
||||
@@ -64,90 +66,60 @@ export async function saveNotdienstAvailabilities(payloadRaw: {
|
||||
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 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,
|
||||
childId: { in: childrenIds },
|
||||
child: { kitaId, familyId: session.user.familyId! },
|
||||
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,
|
||||
});
|
||||
}
|
||||
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) {
|
||||
console.error("Fehler beim Speichern der Notdienste:", error);
|
||||
return { error: "Ein Fehler ist aufgetreten beim Speichern der Termine." };
|
||||
if (!(error instanceof UserFacingError)) console.error("Fehler beim Speichern der Notdienste:", error);
|
||||
return { error: publicActionError(error, "Ein Fehler ist aufgetreten beim Speichern der Termine.") };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user