feat: add household emergency duty alarms and deadline worker

This commit is contained in:
t.indorf
2026-10-09 11:02:24 +02:00
parent a96ba0fd84
commit 8b32e4a832
34 changed files with 3947 additions and 646 deletions
+89
View File
@@ -0,0 +1,89 @@
import type { NotdienstAlarm } from "@prisma/client";
import {
addDays,
dateSchema,
dateKey,
todayKey,
minuteClock,
} from "@/lib/staff/calculations";
export { dateKey, todayKey };
const berlin = new Intl.DateTimeFormat("en-CA", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hourCycle: "h23",
});
export function berlinInstant(date: string, minute: number) {
dateSchema.parse(date);
const target = new Date(`${date}T00:00:00Z`).getTime() + minute * 60_000;
let value = target;
for (let i = 0; i < 3; i++) {
const parts = Object.fromEntries(
berlin.formatToParts(new Date(value)).map((p) => [p.type, p.value]),
);
const represented = Date.parse(
`${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:00Z`,
);
const diff = target - represented;
if (!diff) return new Date(value);
value += diff;
}
throw new Error(
"Diese Uhrzeit existiert wegen der Zeitumstellung nicht. Bitte eine andere Uhrzeit wählen.",
);
}
export function endOfAlarm(
date: string,
wholeDay: boolean,
endMinute?: number | null,
) {
return wholeDay
? berlinInstant(addDays(date, 1), 0)
: berlinInstant(date, endMinute!);
}
export function durationLabel(
alarm: Pick<NotdienstAlarm, "wholeDay" | "startMinute" | "endMinute">,
) {
return alarm.wholeDay
? "Ganzer Kita-Tag"
: `${minuteClock(alarm.startMinute!)}–${minuteClock(alarm.endMinute!)} Uhr`;
}
export function instantLabel(value: Date | string) {
return new Intl.DateTimeFormat("de-DE", {
timeZone: "Europe/Berlin",
day: "2-digit",
month: "2-digit",
hour: "2-digit",
minute: "2-digit",
}).format(new Date(value));
}
export function alarmLabel(
alarm: Pick<NotdienstAlarm, "status" | "deadline" | "expiresAt">,
now = new Date(),
) {
if (alarm.status === "CANCELLED") return "Storniert";
if (alarm.status === "CONFIRMED") return "Übernahme bestätigt";
if (alarm.status === "DECLINED") return "Klärung erforderlich – Absage";
if (alarm.status === "LATE_CONFIRMATION")
return "Verspätete Zusage – Freigabe erforderlich";
if (alarm.status === "REVIEW_REQUIRED")
return "Klärung erforderlich – bisherige Rückmeldungen prüfen";
if (alarm.deadline <= now || alarm.expiresAt <= now)
return "Klärung erforderlich – Frist abgelaufen";
return "Antwort ausstehend";
}
export function needsClarification(
alarm: Pick<NotdienstAlarm, "status" | "deadline" | "expiresAt">,
now = new Date(),
) {
return (
!["CONFIRMED", "CANCELLED"].includes(alarm.status) &&
(alarm.status !== "PENDING" ||
alarm.deadline <= now ||
alarm.expiresAt <= now)
);
}