Phase 10: Mandantenfähigkeit (#15)

This commit was merged in pull request #15.
This commit is contained in:
2026-09-05 13:47:46 +00:00
parent 2b53556175
commit 91742a0dd7
98 changed files with 3810 additions and 201 deletions
+138 -6
View File
@@ -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 |
|---|---|---|
+27 -1
View File
@@ -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
+103 -1
View File
@@ -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
---
+37 -6
View File
@@ -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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 KiB

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 745 KiB

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 KiB

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 KiB

After

Width:  |  Height:  |  Size: 789 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 807 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 KiB

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 638 KiB

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 959 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB