feat: Fristen, Pflichtfristen aus dem Bestellungsdatum und ICS-Feed

Die gesetzlichen Fristen entstehen von selbst, sobald das
Bestellungsdatum in den Stammdaten steht - Vermoegensverzeichnis und
Anfangsbericht nach drei Monaten, Jahresbericht und Vermoegensuebersicht
zum Ende jedes Betreuungsjahres, Aufwandspauschale, Landespflegegeld,
Zuzahlungsbefreiung, Rueckkaufswerte.

Zwei Punkte, an denen es sonst schiefgeht:

Das Betreuungsjahr endet am Tag VOR dem Jahrestag (§ 188 Abs. 2 BGB),
bei Bestellung am 29. Februar am 28. (§ 188 Abs. 3). Ohne diese Regel
verrutscht die ganze Kette um einen Tag.

Faellt eine Frist auf Samstag, Sonntag oder Feiertag, wird sie NICHT
verschoben. § 193 BGB verschiebt zwar den Ablauf, aber als Betreuer will
ich frueher fertig sein, nicht spaeter. Stattdessen steht ein Hinweis in
der Beschreibung. Bayerische Feiertage inklusive, ueber die Osterformel
berechnet.

Pflichtfristen tragen bewusst keine RRULE: sonst entstuende beim
Erledigen eine Dublette neben der ohnehin abgeleiteten Folgefrist. Beim
erneuten Ableiten werden nur offene Fristen nachgezogen, Erledigtes
bleibt unangetastet.

Der ICS-Feed kommt ohne Token-Feld im Schema aus - die Signatur ist ein
HMAC ueber betreuteId und userId. VALARM je Vorwarnstufe, Zeilenfaltung
nach RFC 5545.

51 Tests.

Etappe: 2
Phase: Alltag
This commit is contained in:
Florian Wilde
2026-08-22 02:24:32 +02:00
parent 1217725076
commit 0cdd4edfdc
4 changed files with 663 additions and 2 deletions
+86
View File
@@ -0,0 +1,86 @@
# Schema-Wünsche aus dem Modul „Fristen & Kalender"
Das Modul ist vollständig mit dem bestehenden `model Frist` umgesetzt —
nichts davon blockiert. Die folgenden Punkte sind Verbesserungen, die
jeweils eine Behelfslösung ersetzen würden. `prisma/schema.prisma` wurde
nicht angefasst.
---
## 1. Getrennte Felder für Erledigungsnotiz, Verschiebungs- und Entfallgrund
**Behelf heute:** Beim Erledigen, Verschieben und Entfallenlassen hängen die
Server Actions den Grund als Textzeile an `beschreibung` an
(`"Verschoben vom 14.03.2026: Unterlagen fehlten noch."`).
**Warum das stört:** `beschreibung` ist der Text, der sagt, *was zu tun
ist* — er wandert unverändert in den ICS-Feed und in die Vorwarnungen. Mit
angehängter Historie wird er über die Jahre unlesbar, und die Ableitung der
Pflichtfristen überschreibt ihn bei offenen Fristen ohnehin wieder (dann
ist die Notiz weg). Ausserdem lässt sich nicht auswerten, wie oft eine
Frist verschoben wurde — genau die Zahl, die im Jahresbericht auffällt.
**Wunsch:**
```prisma
/// Freitext beim Abhaken: wie wurde erledigt, was ging raus, an wen.
erledigtNotiz String?
/// Grund der letzten Verschiebung bzw. des Entfallens.
statusGrund String?
/// Wie oft wurde diese Frist schon verschoben? Fuer die Selbstkontrolle.
verschobenAnzahl Int @default(0)
```
---
## 2. Wer hat die Frist erledigt?
**Behelf heute:** Steht nur im Audit-Log (`AuditLog.userId`).
**Warum das stört:** Für die Anzeige („erledigt am 14.03. von Florian
Wilde") müsste die Liste für jede Frist im Audit-Log nachschlagen. Bei
mehreren Betreuern oder einem mitlesenden Geschwisterteil ist das die erste
Frage, die gestellt wird.
**Wunsch:** analog zu `Vorgang.angelegtVonId`
```prisma
erledigtVonId String?
erledigtVon User? @relation("FristErledigtVon", fields: [erledigtVonId], references: [id], onDelete: SetNull)
```
---
## 3. `vorgaengerFristId` ist `@unique` — die Kette ist einspurig
**Behelf heute:** Funktioniert für den Normalfall (eine erledigte Frist →
genau eine Nachfolgefrist).
**Warum das stört:** Beim Verschieben *und* bei der Wiederholung zeigt die
neue Frist auf dieselbe Vorgängerin. Wird eine verschobene Frist später
irrtümlich noch einmal verschoben, läuft das in eine Verletzung der
Eindeutigkeit statt in eine verständliche Meldung. Ein einfacher `@@index`
statt `@unique` würde reichen; die Kette bliebe über `vorgaengerFristId`
weiterhin rückwärts lesbar.
---
## 4. Kein Feld für den Kalender-Token — bewusst so
Zur Klarstellung, damit niemand später eines nachrüstet: Der abonnierbare
ICS-Feed kommt **ohne** Schemafeld aus. Die Adresse trägt eine Signatur
`base64url(<betreuteId>.<userId>.<HMAC-SHA256>)`, berechnet mit
`AUTH_SECRET` (siehe `src/lib/fristen/ics.ts`).
Vorteil: Es liegt kein dauerhaft gültiges Geheimnis in der Datenbank, das
bei einem Datenbank-Leck mitgelesen werden könnte. Die Route prüft bei
jedem Abruf zusätzlich, ob der Benutzer noch aktiv ist und noch Lesezugriff
auf das Modul `FRISTEN` hat — ein entzogener Zugang leert den Kalender
sofort.
Nachteil, der in Kauf genommen wird: Eine einzelne Adresse lässt sich nicht
gezielt widerrufen, nur durch Wechsel von `AUTH_SECRET` (was alle
Anmeldungen beendet). Falls das je gebraucht wird, wäre ein
`kalenderGeneration Int @default(0)` an `BetreuungsZugang` die kleinste
Lösung: Sie geht in den HMAC ein, und ein Hochzählen entwertet nur die
Adressen dieses einen Zugangs.