Phase 10: Mandantenfähigkeit (#15)
@@ -1,6 +1,6 @@
|
||||
# Anleitung
|
||||
|
||||
Bedienung von FLORIAN, Stand Phase 9 (Version 0.10.0). Die Bilder zeigen die
|
||||
Bedienung von FLORIAN, Stand Phase 10 (Version 0.11.0). Die Bilder zeigen die
|
||||
Beispielwehr aus dem Seed und werden mit `npm run screenshots` neu erzeugt.
|
||||
|
||||
---
|
||||
@@ -1986,7 +1986,138 @@ Dezimaltrennzeichen, UTF-8 mit Kennung.
|
||||
|
||||
---
|
||||
|
||||
## 54. Rollen und was sie dürfen
|
||||
## 54. Mehrere Wehren auf einem Server
|
||||
|
||||
Ein FLORIAN kann mehrere Wehren führen — eine Gemeinde mit drei Ortswehren,
|
||||
ein Landkreis mit zwanzig. Die Wehren sehen einander dabei **nicht**. Jede
|
||||
Abfrage ist an eine Wehr gebunden, und zwar in der Datenbank selbst, nicht im
|
||||
Programm.
|
||||
|
||||
### Konto und Personenakte
|
||||
|
||||
Zwei verschiedene Dinge, und der Unterschied ist der Kern dieses Kapitels:
|
||||
|
||||
| | Was es ist |
|
||||
|---|---|
|
||||
| **Konto** | Womit Sie sich anmelden. E-Mail und Passwort. Gehört keiner Wehr. |
|
||||
| **Personenakte** | Ihr Eintrag in einer Wehr: Dienstgrad, Qualifikationen, Einsätze. |
|
||||
| **Zuordnung** | Verbindet beides — je Wehr eine. |
|
||||
|
||||
Wer nur zu einer Wehr gehört, merkt davon nichts: ein Konto, eine Akte, eine
|
||||
Zuordnung, und alles sieht aus wie vorher.
|
||||
|
||||
Wer zu **zwei** Wehren gehört — aktiv in der einen, Ehrenmitglied in der
|
||||
anderen —, hat trotzdem nur **ein** Passwort. Zwei Akten, ein Konto.
|
||||
|
||||
### Die Wehr wechseln
|
||||
|
||||
Steht Ihr Konto bei mehreren Wehren, wird der Name der Wehr in der
|
||||
Seitenleiste zu einem Auswahlfeld. Ein Klick darauf, und alle Wehren stehen
|
||||
untereinander — mit dem Hinweis, ob Sie dort Mitglied oder Kreisaufsicht sind.
|
||||
|
||||
Nach der Anmeldung sind Sie immer in **Ihrer eigenen** Wehr, nicht in einer
|
||||
betreuten.
|
||||
|
||||
Nach dem Wechsel wird alles neu geladen. Was Sie danach sehen, gehört zur
|
||||
neuen Wehr — es gibt keinen Zustand, in dem Daten der einen unter dem Namen
|
||||
der anderen stehen.
|
||||
|
||||
---
|
||||
|
||||
## 55. Kreisbrandrat und Kreisbrandinspektion
|
||||
|
||||
Die Kreisebene bekommt Zugang zu den betreuten Wehren, ohne ihnen anzugehören.
|
||||
|
||||
**Sie sieht und ändert nichts.** Das ist keine Einstellung, die jemand
|
||||
versehentlich anders setzen kann: eine Aufsichtszuordnung bekommt genau die
|
||||
Leserechte und keines der Schreibrechte, und zwar automatisch auch für Module,
|
||||
die es noch gar nicht gibt.
|
||||
|
||||
Wer als Aufsicht in einer Wehr unterwegs ist, sieht das durchgehend als Zeile
|
||||
über der Ansicht.
|
||||
|
||||
### Was die Aufsicht sieht
|
||||
|
||||
Mitglieder, Fahrzeuge, Geräte, Prüfungen, Atemschutz, Einsätze, Übungen,
|
||||
Qualifikationen, Termine, Wiki, Karte.
|
||||
|
||||
Der **Atemschutz** ist dabei der wichtigste Punkt: ob ein Träger tauglich ist,
|
||||
ist genau die Frage, für die es eine Kreisebene gibt.
|
||||
|
||||
### Was sie nicht sieht
|
||||
|
||||
| | Warum |
|
||||
|---|---|
|
||||
| **Kasse** | Sie gehört dem Verein oder der Kommune, nicht der Kreisaufsicht. |
|
||||
| **Protokoll** | Wer wann was getan hat, bleibt innerhalb der Wehr. |
|
||||
| **KI-Protokoll** | Dasselbe. |
|
||||
| **Personenexport nach DSGVO** | Das Auskunftsrecht erfüllt die Wehr, die die Daten führt. |
|
||||
|
||||
### Sie taucht in keiner Mitgliederliste auf
|
||||
|
||||
Die Aufsicht hat in der betreuten Wehr **keine Personenakte**. Sie ist also
|
||||
kein Mitglied, erscheint in keiner Liste, in keiner Stärkemeldung und in
|
||||
keiner Anwesenheit — und kann als Urheber einer Änderung auch gar nicht
|
||||
eingetragen werden, weil sie keine macht.
|
||||
|
||||
Ist der Kreisbrandinspektor zugleich Kommandant seiner Heimatwehr, hat er dort
|
||||
ganz normal seine Akte und seine Rollen. Es sind zwei Zuordnungen desselben
|
||||
Kontos, mehr nicht.
|
||||
|
||||
---
|
||||
|
||||
## 56. Wehren und Konten verwalten
|
||||
|
||||
Unter **Wehren und Konten**. Der Menüpunkt erscheint nur für den, der die
|
||||
Installation betreibt — nicht für Kommandanten, auch nicht für
|
||||
Administratoren einer einzelnen Wehr.
|
||||
|
||||
> Dieses Merkmal hebt die Trennung zwischen den Wehren auf. Es gehört an so
|
||||
> wenige Konten wie möglich, im Regelfall an eines.
|
||||
|
||||
### Eine Wehr anlegen
|
||||
|
||||
Vollständiger Name, Kurzname und eine **Kennung**. Die Kennung steht später
|
||||
im Terminal und in Dateipfaden und lässt sich nicht mehr ändern — also mit
|
||||
Bedacht wählen: Kleinbuchstaben, Ziffern, Bindestriche, etwa
|
||||
`ff-musterhausen`.
|
||||
|
||||
Die neue Wehr bekommt sofort ihre Rollen samt Rechten. Ein Konto bekommt sie
|
||||
nicht: das ist der zweite Schritt.
|
||||
|
||||
### Ein Konto anlegen
|
||||
|
||||
E-Mail-Adresse und ein erstes Passwort. Die Adresse gilt über alle Wehren
|
||||
hinweg und ist damit die Anmeldung.
|
||||
|
||||
Das Passwort steht im Klartext da, damit Sie es weitergeben können — FLORIAN
|
||||
verschickt keines. Es gehört nach der ersten Anmeldung geändert.
|
||||
|
||||
Gibt es zu der Adresse schon ein Konto, sagt FLORIAN das und legt **kein
|
||||
zweites** an. Wenn diese Person zu einer weiteren Wehr gehören soll, bekommt
|
||||
sie eine weitere Zuordnung — genau dafür ist die Trennung da.
|
||||
|
||||
### Zuordnen
|
||||
|
||||
Konto wählen, **Wehr zuordnen**, dann:
|
||||
|
||||
- **Mitglied** — zusätzlich die Personenakte in dieser Wehr wählen. Zur
|
||||
Auswahl steht, wer noch kein Konto hat: eine Akte gehört zu höchstens einem,
|
||||
sonst wäre nicht entscheidbar, wer unter ihrem Namen gehandelt hat.
|
||||
- **Kreisaufsicht** — keine Akte, nur Leserechte.
|
||||
|
||||
Jede Zuordnung wird **in der betroffenen Wehr** protokolliert. Dort steht also
|
||||
nachlesbar, dass jemand von außen Zugang bekommen hat.
|
||||
|
||||
### Eine Zuordnung beenden
|
||||
|
||||
Über das Papierkorb-Symbol. Die Personenakte in der Wehr bleibt bestehen —
|
||||
entfernt wird nur der Zugang. Wer ausgetreten ist, wird in der Mitgliederliste
|
||||
ausgetragen, nicht hier.
|
||||
|
||||
---
|
||||
|
||||
## 57. Rollen und was sie dürfen
|
||||
|
||||
Neun Rollen, jede mit einem festen Satz Rechte. Die Zuordnung steht zentral in
|
||||
`src/lib/authz.ts` und wird beim Seed in die Datenbank gespiegelt — eine Wehr
|
||||
@@ -2002,6 +2133,7 @@ kann sie also nachträglich zuschneiden, ohne dass jemand Code ändert.
|
||||
| **Schriftführer** | Mitglieder pflegen, Einsätze und Berichte erfassen, den Terminkalender führen |
|
||||
| **Ausbilder** | Übungen und Übungstermine planen, Teilnahme, Anwesenheit und Qualifikationen erfassen |
|
||||
| **Mitglied** | eigene Daten, Listen lesen, Termine sehen und sich zurückmelden, eigene Benachrichtigungswege einrichten, im Wiki lesen |
|
||||
| **Kreisebene** | sieht die betreuten Wehren, ändert nichts — siehe [Kapitel 55](#55-kreisbrandrat-und-kreisbrandinspektion) |
|
||||
| **Terminal** | nur anmelden und erfassen — nie verwalten |
|
||||
|
||||
Zwei Dinge sind bewusst so gebaut:
|
||||
@@ -2020,7 +2152,7 @@ sind nur die Textvorlagen, und die hängen an einem eigenen Recht.
|
||||
|
||||
---
|
||||
|
||||
## 55. Anmeldung über authentik einrichten
|
||||
## 58. Anmeldung über authentik einrichten
|
||||
|
||||
authentik läuft **getrennt** von FLORIAN und ist nicht Teil dieses Stacks.
|
||||
|
||||
@@ -2073,7 +2205,7 @@ ganze Wehr aus.
|
||||
|
||||
---
|
||||
|
||||
## 56. PIN und Ausweiskarte verwalten
|
||||
## 59. PIN und Ausweiskarte verwalten
|
||||
|
||||
Wie sich am Terminal angemeldet wird, steht in Kapitel 24. Hier geht es darum,
|
||||
was dahinter liegt.
|
||||
@@ -2096,7 +2228,7 @@ Huber), PIN `471203`.
|
||||
|
||||
---
|
||||
|
||||
## 57. Was protokolliert wird
|
||||
## 60. Was protokolliert wird
|
||||
|
||||
Jede schreibende Änderung erzeugt einen Eintrag mit Zeitpunkt, Person,
|
||||
betroffenem Datensatz und den geänderten Feldern im Vorher-Nachher-Vergleich.
|
||||
@@ -2121,7 +2253,7 @@ jeweiligen Detailseite sichtbar.
|
||||
|
||||
---
|
||||
|
||||
## 58. Wenn etwas nicht geht
|
||||
## 61. Wenn etwas nicht geht
|
||||
|
||||
| Beobachtung | Wahrscheinliche Ursache | Was hilft |
|
||||
|---|---|---|
|
||||
|
||||
@@ -40,7 +40,8 @@ Faktoren, die in die Schätzung eingehen:
|
||||
| 7 | Benachrichtigungen und Termine | 10–13 PT | 2,5–3,5 h | **3:10 h** |
|
||||
| 8 | Lokale KI | 9–12 PT | 2,5–3 h | **3:40 h** |
|
||||
| 9 | Kassenverwaltung | 8–11 PT | 2–3 h | **4:10 h** |
|
||||
| | **Summe** | **123–164 PT** | **28–37 h** | **28:30 h** |
|
||||
| 10 | Mandantenfähigkeit | 4–6 PT | 1–1,5 h | **4:20 h** |
|
||||
| | **Summe** | **127–170 PT** | **29–38,5 h** | **32:50 h** |
|
||||
|
||||
123–164 Personentage entsprechen etwa **6 bis 8 Monaten Vollzeitarbeit** einer
|
||||
einzelnen Person. Zum Vergleich: eine kommerzielle Feuerwehrverwaltung kostet
|
||||
@@ -432,6 +433,31 @@ der Prüfbericht, der nach dem Festschreiben entsteht, und die
|
||||
DSGVO-Löschung einer Wehr, die an ihren eigenen festgeschriebenen Buchungen
|
||||
scheiterte.
|
||||
|
||||
## Phase 10 — Mandantenfähigkeit
|
||||
|
||||
**Vergleichsaufwand: 4–6 PT** — aus der Kandidatenliste vorgezogen, am
|
||||
05.09.2026 umgesetzt.
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| `Benutzer` und `Mitgliedschaft`, datenerhaltende Umstellung | 1,5 |
|
||||
| Anmeldung: Konto statt Akte, Wehrwechsel im Sitzungstoken | 1,5 |
|
||||
| Kreisebene mit reinen Leserechten | 1,0 |
|
||||
| Wehrwähler und Verwaltung der Installation | 1,5 |
|
||||
|
||||
Die Schätzung ging von „technisch bereits möglich, Oberfläche fehlt" aus —
|
||||
und das war zur Hälfte falsch. Datenmodell und RLS trugen tatsächlich mehrere
|
||||
Wehren, aber die Anmeldung nicht: Passwort und OIDC-Subject hingen an der
|
||||
Personenakte, und die gehört genau einer Wehr. Das war kein Oberflächenthema,
|
||||
sondern ein Umbau am Fundament.
|
||||
|
||||
**Tatsächlich: 4:20 h.** Getragen hat eine Entscheidung ganz am Anfang: die
|
||||
Sitzung behält ihre Form und entsteht nur anders. Ohne sie wären 61 Dateien
|
||||
mit 414 Fundstellen zu ändern gewesen; so waren es acht. Die Zeit ging
|
||||
stattdessen in die Absicherung — zwei Trigger, vier CHECKs und elf
|
||||
Datenbanktests — und das ist die richtige Verteilung für eine Phase, deren
|
||||
einziger Zweck es ist, dass eine Wehr die andere nicht sieht.
|
||||
|
||||
---
|
||||
|
||||
## Was hier nicht drin ist
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ Phase 6 ████████████████████ fertig
|
||||
Phase 7 ████████████████████ fertig 0.8.0
|
||||
Phase 8 ████████████████████ fertig 0.9.0
|
||||
Phase 9 ████████████████████ fertig 0.10.0
|
||||
Phase 10 ████████████████████ fertig 0.11.0
|
||||
Freigabe ░░░░░░░░░░░░░░░░░░░░ offen 1.0.0
|
||||
```
|
||||
|
||||
@@ -260,6 +261,35 @@ ein Skript oder ein Import würden sonst reichen, um den geprüften Bestand zu
|
||||
**Die Wehr kann jetzt:** ihre Kasse führen und den Kassenprüfbericht auf
|
||||
Knopfdruck erzeugen.
|
||||
|
||||
## Phase 10 — Mandantenfähigkeit · fertig
|
||||
|
||||
Am 05.09.2026 ergänzt. Datenmodell und RLS trugen seit Phase 0 mehrere Wehren;
|
||||
was fehlte, war alles darüber.
|
||||
|
||||
- `Benutzer` und `Mitgliedschaft`: das Anmeldekonto ist von der Personenakte
|
||||
gelöst. Ein Mensch, ein Passwort, beliebig viele Wehren.
|
||||
- Wehrwähler in der Seitenleiste — er erscheint nur, wenn es etwas zu wählen
|
||||
gibt.
|
||||
- Die Rolle `AUFSICHT` für die Kreisebene: Kreisbrandrat und
|
||||
Kreisbrandinspektion sehen die betreuten Wehren und ändern nichts.
|
||||
- Verwaltung der Installation unter `/wehren`: Wehren anlegen, Konten führen,
|
||||
Zuordnungen vergeben.
|
||||
|
||||
Die Entscheidung, die alles andere bestimmt: **das Konto trägt keine
|
||||
Mandantenkennung.** Es kann keine tragen — es ist ja der Angelpunkt, an dem
|
||||
ein Mensch zu mehreren Wehren gehört. Geschützt wird es deshalb wie
|
||||
`Berechtigung` über Rechte statt über Row Level Security, und was ein Konto in
|
||||
einer Wehr darf, entscheidet nicht es selbst, sondern seine `Mitgliedschaft` —
|
||||
und die trägt `organisationId` wie jede andere Tabelle.
|
||||
|
||||
**Bewusst nicht dabei:** wehrübergreifende Auswertungen. Eine Zahl über
|
||||
mehrere Wehren hinweg braucht eine abgestimmte Definition, und ohne die ist
|
||||
sie schlimmer als keine. Die Aufsicht sieht jede Wehr einzeln.
|
||||
|
||||
**Die Wehr kann jetzt:** sich einen Server mit anderen teilen, ohne dass
|
||||
jemand über die Grenze sieht — und die Kreisebene kann hineinschauen, ohne
|
||||
etwas anfassen zu können.
|
||||
|
||||
## 1.0.0 — Freigabe
|
||||
|
||||
Kein neues Modul, sondern eine Entscheidung: die Wehr setzt FLORIAN im
|
||||
@@ -282,6 +312,9 @@ Am 03.09.2026 entschieden:
|
||||
|---|---|
|
||||
| Kassenmodul | Als **Phase 9** ergänzt und mit 0.10.0 umgesetzt. Spezifikation in `Phase-9.md`. |
|
||||
| `Berechtigung` ohne `organisationId` | Bleibt so. Die Tabelle ist der systemweite Katalog dessen, was die Software kann, für alle Wehren identisch — mandantenbezogen wird es erst über `RolleBerechtigung`. Für die Anwendungsrolle nur lesbar. |
|
||||
| `Benutzer` ohne `organisationId` | Notwendige Ausnahme, seit Phase 10. Die Tabelle ist der Angelpunkt der Doppelmitgliedschaft — mit Mandantenkennung könnte sie das nicht sein. Geschützt über Rechte statt RLS, wie `Berechtigung`: die Anwendungsrolle darf lesen und die Anmeldefelder fortschreiben, mehr nicht. |
|
||||
| `AUFSICHT` nicht im Prisma-Enum | Die Kreisebene ergibt sich aus der Mitgliedschaftsart, nicht aus einer Rollenzuweisung. Vergebbar zu machen hiesse, dass jemand innerhalb einer Wehr Rechte an einer anderen verteilen könnte. |
|
||||
| `istVerwalter` als Kontomerkmal | Kein Recht. Rechte gelten je Wehr; wer Wehren anlegt, steht über allen. Ein Recht, das in Wehr A vergeben wird und in Wehr B wirkt, wäre die Hintertür, die Phase 10 vermeiden soll. |
|
||||
| Löschausnahme im Audit-Log | Bleibt so. `UPDATE` ist dauerhaft gesperrt, `DELETE` nur mit `SET LOCAL app.audit_bereinigung = 'an'` im Aufbewahrungslauf des Workers. Ohne diese Ausnahme wäre das Löschkonzept nach DSGVO nicht umsetzbar. |
|
||||
|
||||
## Weitere Kandidaten für später
|
||||
@@ -292,10 +325,8 @@ Am 03.09.2026 entschieden:
|
||||
| Kleiderkammer und Wäscheverwaltung | in Phase 4 nur als Wizard, ohne Bestandsführung | 4–6 PT |
|
||||
| Jugendfeuerwehr mit eigener Abzeichenverwaltung | eigene Fachlichkeit | 5–7 PT |
|
||||
| Schnittstelle zur Integrierten Leitstelle | Alarmdaten automatisch übernehmen | 6–10 PT, stark abhängig von der ILS |
|
||||
| Mehrere Wehren je Installation | technisch bereits möglich, Oberfläche fehlt | 4–6 PT |
|
||||
|
||||
Die Mandantenfähigkeit ist der Punkt, der ohne Zusatzaufwand am weitesten
|
||||
trägt: das Datenmodell und RLS können bereits mehrere Wehren. Was fehlt, ist
|
||||
eine Oberfläche zum Umschalten und ein Konzept für gemeindeübergreifende
|
||||
Auswertungen. Für einen Landkreis, der mehrere Wehren betreiben will, wäre das
|
||||
der nächste sinnvolle Schritt.
|
||||
Die Mandantenfähigkeit ist mit **Phase 10** umgesetzt: umschalten, Kreisebene,
|
||||
Doppelmitgliedschaft, Verwaltung. Offen geblieben sind bewusst die
|
||||
gemeindeübergreifenden Auswertungen — dafür braucht es zuerst eine abgestimmte
|
||||
Definition dessen, was verglichen werden soll.
|
||||
|
||||
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 413 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 745 KiB After Width: | Height: | Size: 803 KiB |
|
Before Width: | Height: | Size: 764 KiB After Width: | Height: | Size: 824 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 727 KiB After Width: | Height: | Size: 789 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 807 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 263 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 627 KiB After Width: | Height: | Size: 628 KiB |
|
Before Width: | Height: | Size: 638 KiB After Width: | Height: | Size: 639 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 959 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 359 KiB |