Improve family invitations and absence management flows

This commit is contained in:
t.indorf
2026-10-09 22:59:36 +02:00
parent 4702bf5abb
commit 8915fe7df6
29 changed files with 2803 additions and 617 deletions
+73
View File
@@ -0,0 +1,73 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import {
absenceDate,
absenceToday,
canEditOwnAbsence,
canManageKitaAbsences,
} from "../src/lib/absence-policy";
import { familyInvitationView } from "../src/lib/family-invitation-view";
test("Abwesenheitsdatum akzeptiert echte Kalendertage und lehnt Überläufe ab", () => {
assert.equal(
absenceDate("2028-02-29").toISOString(),
"2028-02-29T00:00:00.000Z",
);
for (const value of [
"2026-02-29",
"2026-04-31",
"2026-13-01",
"2026-1-09",
"2026-10-09T12:00:00",
"invalid",
])
assert.throws(() => absenceDate(value));
});
test("Die Datumsgrenze folgt Berlin auch über Mitternacht und die Zeitumstellung", () => {
assert.equal(absenceToday(new Date("2026-10-09T21:59:59Z")), "2026-10-09");
assert.equal(absenceToday(new Date("2026-10-09T22:00:00Z")), "2026-10-10");
assert.equal(absenceToday(new Date("2026-10-25T22:59:59Z")), "2026-10-25");
assert.equal(absenceToday(new Date("2026-10-25T23:00:00Z")), "2026-10-26");
assert.equal(canEditOwnAbsence("2026-10-09", "2026-10-09"), false);
assert.equal(canEditOwnAbsence("2026-10-10", "2026-10-09"), true);
assert.equal(canManageKitaAbsences("ELTERN"), false);
for (const role of ["ADMIN", "KOORDINATOR", "ERZIEHER"])
assert.equal(canManageKitaAbsences(role), true);
});
test("Einladungsanzeige unterscheidet Aktivierung, Fehler, Ablauf und laufenden Versand", () => {
const now = new Date("2026-10-09T12:00:00Z");
const base = {
expiresAt: new Date("2026-10-16T12:00:00Z"),
sentAt: null,
failed: false,
leaseUntil: null,
status: "PENDING",
};
assert.equal(familyInvitationView(null, null, now).status, "open");
assert.equal(
familyInvitationView(null, { ...base, failed: true }, now).status,
"failed",
);
assert.equal(
familyInvitationView(null, { ...base, expiresAt: now }, now).status,
"expired",
);
assert.equal(
familyInvitationView(null, { ...base, sentAt: now }, now).status,
"sent",
);
assert.equal(
familyInvitationView(
null,
{ ...base, leaseUntil: new Date(now.getTime() + 1000) },
now,
).status,
"sending",
);
assert.equal(
familyInvitationView(now, { ...base, failed: true }, now).status,
"active",
);
});