Phase 10: Mandantenfähigkeit (#15)
This commit was merged in pull request #15.
This commit is contained in:
+103
-1
@@ -22,7 +22,109 @@ Die aufgewendete Zeit wandert nach jedem Abschnitt in die Spalte
|
||||
| Phase 8 — Lokale KI | 3:40 h | 1 | 04.09.2026 |
|
||||
| Aufräumarbeiten (Issues #10–#12) | 1:20 h | 1 | 04.09.2026 |
|
||||
| Phase 9 — Kassenverwaltung | 4:10 h | 1 | 04.09.2026 |
|
||||
| **Summe** | **28:30 h** | **12** | |
|
||||
| Phase 10 — Mandantenfähigkeit | 4:20 h | 1 | 05.09.2026 |
|
||||
| **Summe** | **32:50 h** | **13** | |
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-05 · Phase 10 — Mandantenfähigkeit
|
||||
|
||||
**08:10 – 12:30 · 4:20 h · Version 0.11.0**
|
||||
|
||||
### Was entstanden ist
|
||||
|
||||
Die einzige Phase bisher, die die Anmeldung selbst anfasst.
|
||||
|
||||
| Bereich | Umfang |
|
||||
|---|---|
|
||||
| Datenmodell | `Benutzer`, `Mitgliedschaft`, `MitgliedschaftArt`; `Person` verliert die Anmeldefelder |
|
||||
| Absicherung | RLS für `mitgliedschaft`, zwei Trigger, vier CHECKs, ein REVOKE |
|
||||
| Anmeldung | `lib/auth.ts` neu: Konto statt Akte, Mitgliedschaftswahl, Wehrwechsel im Token |
|
||||
| Rechte | Rolle `AUFSICHT`, gefiltert statt aufgezählt; `istVerwalter` als Kontomerkmal |
|
||||
| Oberfläche | Wehrwähler, Aufsichtshinweis, `/wehren` zur Verwaltung |
|
||||
| Tests | 19 neue Unit-Tests (11 gegen die Datenbank), 13 E2E-Tests |
|
||||
|
||||
### Entscheidungen
|
||||
|
||||
**Die Sitzung behält ihre Form.** `sitzung.organisationId` und
|
||||
`sitzung.personId` werden an 329 bzw. 85 Stellen in 61 Dateien gelesen. Hätte
|
||||
sich ihre Bedeutung geändert, wäre das ein Umbau durch das halbe Projekt
|
||||
geworden. Stattdessen entsteht die Sitzung nur *anders* — aus der aktiven
|
||||
Mitgliedschaft statt aus der Person. Keine dieser 61 Dateien musste angefasst
|
||||
werden.
|
||||
|
||||
**`personId` ist nullbar geworden.** Das war die eine Ausnahme davon, und zwar
|
||||
absichtlich: eine Aufsichtssitzung hat keine Akte. Der Typechecker hat daraus
|
||||
eine Liste gemacht — genau fünf Stellen verlangen eine Person, und alle fünf
|
||||
sind Dinge, an denen die Kreisaufsicht nichts verloren hat (Anmeldemarke,
|
||||
Dienstausweis, Prüfungswizard, Dateiupload, Atemschutz). Ein `?? ""` hätte
|
||||
dieselben fünf Stellen still passieren lassen.
|
||||
|
||||
**`Benutzer` ohne `organisationId`.** Die zweite Ausnahme von der
|
||||
Architekturregel nach `Berechtigung` — und dieselbe Begründung: die Tabelle
|
||||
steht über allen Mandanten, weil sie der Angelpunkt ist, an dem ein Mensch zu
|
||||
mehreren Wehren gehört. Geschützt über Rechte statt RLS.
|
||||
|
||||
**`AUFSICHT` steht nicht im Prisma-Enum.** Alle anderen Rollen werden einem
|
||||
Menschen in einer Wehr zugewiesen. Diese ergibt sich aus der Mitgliedschaftsart.
|
||||
Sie vergebbar zu machen hiesse, dass jemand innerhalb einer Wehr Rechte an
|
||||
einer anderen verteilen könnte.
|
||||
|
||||
**Ihre Rechte werden gefiltert, nicht aufgezählt.** Alles, was `.lesen` heißt,
|
||||
minus vier ausdrückliche Ausnahmen. Eine Liste von Hand wäre bei Phase 11
|
||||
veraltet, und zwar unbemerkt — ein neues Modul brächte Rechte mit, die niemand
|
||||
nachträgt. So bekommt die Aufsicht neue Leserechte automatisch und neue
|
||||
Schreibrechte nie. Der Test dazu ist entsprechend formuliert: nicht „diese
|
||||
dreizehn Rechte", sondern „kein einziges, das nicht lesen heißt".
|
||||
|
||||
**Die Kasse ist ausgenommen**, obwohl `kasse.lesen` auf `.lesen` endet. Sie
|
||||
gehört dem Verein oder der Kommune, nicht der Kreisaufsicht. Sie wäre nur
|
||||
deshalb dabei gewesen, weil das Recht zufällig richtig heißt — und genau
|
||||
dagegen ist die Ausnahmeliste da.
|
||||
|
||||
**Die Migration ist datenerhaltend, mit Abbruchprüfung.** Konten und
|
||||
Zuordnungen entstehen, *bevor* die alten Spalten fallen; am Ende prüft ein
|
||||
`DO`-Block, dass keine Person mit Anmeldedaten ohne Konto dasteht, und bricht
|
||||
sonst ab. Eine Migration, die die Anmeldung anfasst und auf halbem Weg
|
||||
liegenbleibt, sperrt alle aus.
|
||||
|
||||
### Wo es gehakt hat
|
||||
|
||||
| Problem | Ursache | Lösung |
|
||||
|---|---|---|
|
||||
| Nach dem Wehrwechsel landete alles auf der Anmeldeseite | `aktuelleSitzung()` verlangte eine Personenkennung — die Aufsicht hat keine und galt damit als nicht angemeldet | Eine Sitzung ist gültig, wenn Wehr und Konto feststehen; die Akte darf fehlen |
|
||||
| Der Wechsel schien nicht zu greifen | Er griff: das Token trug die neue Wehr. Es war der dritte hängengebliebene Entwicklungsserver dieser Woche — `npm run build` neben laufendem `next dev` macht `.next` unbrauchbar, und der Server antwortet danach nur noch mit 500 | Server beenden, `.next` löschen. Diesmal notiert, damit es nicht ein viertes Mal passiert |
|
||||
| Der Seed brach ab | Er legt die Rollen aus `ROLLEN` an, und dort steht jetzt `AUFSICHT` — die es im Prisma-Enum bewusst nicht gibt | Im Seed gefiltert, mit Begründung |
|
||||
| `unstable_update` nahm den Wechselwunsch nicht an | Es akzeptiert nur `Partial<Session>` | Der Wunsch reist als `user.mitgliedschaftId` und wird serverseitig gegen die Mitgliedschaften geprüft |
|
||||
| Die Nachbarwehr war fest verdrahtet | `ff-nachbarshausen` für jeden Mandanten — genau die Vermischung von Test- und Beispieldaten, die Phase 8 beseitigt hat | Aus dem Mandanten abgeleitet: `<slug>-nachbar` |
|
||||
| Ein alter Datenbanktest wurde unzuverlässig | `ki-datenbank.test.ts` legte Wehr und Person idempotent an, die Wiki-Seite aber nicht. Die neue Testdatei verschob die Reihenfolge, und ein Rest aus einem abgebrochenen Lauf ließ den ganzen Datei-Lauf scheitern | Auch dort ein `upsert` |
|
||||
| Nach `db:test-reset` zeigte der Wehrwähler nur noch die betreute Wehr | Der Reset löschte die Testwehr, aber nicht ihre Nachbarwehr. Der Seed sah die Nachbarwehr, hielt alles für erledigt und legte die Mitgliedschaft in der neuen Testwehr nie wieder an | Der Reset nimmt Nachbarwehr und Kreiskonto mit; der Seed prüft beide Teile getrennt statt mit einem gemeinsamen Ausstieg. Gefunden hat es ein E2E-Test |
|
||||
| Im vollen Lauf fielen fremde Testdateien mit „Anmeldung fehlgeschlagen“ aus | Der Fehlversuch-Test meldete sich mit der Adresse des Administrators an. Fünf Fehlversuche sperren das Konto fünf Minuten — bei zwei Projekten und Wiederholungen kam das zusammen, und alle anderen Dateien brauchen dasselbe Konto | Der Test nimmt jetzt eine Adresse, die es nicht gibt. Die Meldung muss ohnehin dieselbe sein, damit sie nicht verrät, ob es die Adresse gibt |
|
||||
|
||||
### Abweichung von der Vorgabe
|
||||
|
||||
**Zwei Architekturregeln bekommen eine dokumentierte Ausnahme.**
|
||||
|
||||
„Jede Tabelle hat `organisationId`" gilt für `Benutzer` nicht — sie kann es
|
||||
nicht, ohne den Zweck der Tabelle aufzuheben. Die Ausnahme steht im Schema, in
|
||||
der Migration und in der Roadmap, und die Tabelle ist dafür über Rechte
|
||||
geschützt statt über RLS.
|
||||
|
||||
„Berechtigungen zentral in `lib/authz.ts`" gilt für den Verwalterzugang nicht:
|
||||
er hängt an `Benutzer.istVerwalter` und nicht an einem Recht. Rechte gelten je
|
||||
Wehr — ein Recht, das in Wehr A vergeben wird und in Wehr B wirkt, wäre die
|
||||
Hintertür, die diese Phase gerade schließen soll.
|
||||
|
||||
### Nachweise
|
||||
|
||||
- `npm run typecheck` — ohne Ausgabe
|
||||
- `npm run lint` — ohne Befund
|
||||
- `npm run test` — 581 Tests grün, davon 11 neu gegen eine echte Datenbank
|
||||
- `npm run test:e2e` — grün über Desktop und Tablet, davon 26 neu
|
||||
- `npm run db:seed` und `npm run db:test-reset` — beide Mandanten samt
|
||||
Nachbarwehr und Kreisebene
|
||||
- Anmeldung nach der Umstellung von Hand geprüft: Konto gefunden, Passwort
|
||||
richtig, Sitzung trägt Konto, Mitgliedschaft und Verwaltermerkmal
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user