Phase 0: Fundament der Feuerwehr-Verwaltungsplattform
Legt den kompletten Unterbau an, auf dem die Fachmodule aufsetzen. Betrieb: - docker-compose.yml mit Traefik, App, Worker, PostgreSQL 17 + PostGIS, Redis, MinIO und Gotenberg; Entwicklungs-Override ohne Traefik, dafuer mit Mailpit und Adminer - Mehrstufiges Dockerfile: eigener Benutzer, tini als PID 1, Healthcheck - .env.example mit einem Kommentar zu jeder Variable Mandantentrennung: - Row Level Security auf allen mandantenpflichtigen Tabellen - Zwei getrennte Datenbankverbindungen: florian_app mit aktiver RLS fuer die Anwendung, Eigentuemer-Zugang nur fuer Migration, Seed und Anmeldung - fuerOrganisation() als einziger Weg zu Fachdaten Nachvollziehbarkeit: - AuditLog append-only per Trigger; UPDATE ist dauerhaft gesperrt, DELETE nur im gekennzeichneten Aufbewahrungslauf - Geheimnisse werden vor dem Schreiben entfernt Anmeldung: - OIDC gegen ein extern betriebenes authentik, Rollen aus Gruppen nur als Vorbelegung beim ersten Login - Lokaler E-Mail/Passwort-Login als Rueckfallweg - Terminal per PIN und RFID mit zwei Schranken (Geraet, dann Person) Weiteres: - lib/authz.ts mit 9 Rollen und 38 Einzelrechten - lib/storage.ts (MinIO), lib/audit.ts, lib/warteschlange.ts (BullMQ) - Integrationen fuer Mail, Telegram und LLM hinter abschaltbaren Interfaces - tRPC v11, Basis-Layout mit Seitenleiste und dunkler Darstellung - Seed mit einer Beispielwehr: 16 Personen, 3 Fahrzeuge, 40 Geraete - 32 Vitest-Tests (davon 8 gegen eine echte Datenbank), 10 Playwright-Tests - README, Anleitung mit Screenshots, Roadmap, Aufwandsschaetzung, Devlog, Versionierungsregeln und Changelog Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
# Anleitung
|
||||
|
||||
Bedienung von FLORIAN, Stand Phase 0 (Version 0.1.0). Die Bilder zeigen die
|
||||
Beispielwehr aus dem Seed und werden mit `npm run screenshots` neu erzeugt.
|
||||
|
||||
> Was hier noch fehlt — Mitglieder, Geräte, Prüfungen — kommt in den
|
||||
> folgenden Phasen dazu. Der Stand ist bewusst schmal: Phase 0 baut das
|
||||
> Fundament, nicht die Fachmodule. Siehe [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Anmelden
|
||||
|
||||
Aufgerufen wird FLORIAN im Browser unter der Adresse, die der Administrator
|
||||
eingerichtet hat — in der Entwicklung `http://localhost:3100`.
|
||||
|
||||

|
||||
|
||||
Die Anmeldeseite zeigt genau die Wege, die in dieser Installation eingerichtet
|
||||
sind:
|
||||
|
||||
| Weg | Wann |
|
||||
|---|---|
|
||||
| **Anmeldung über authentik** | Der Regelweg, sobald authentik eingerichtet ist. Ein Klick, keine Passworteingabe in FLORIAN. |
|
||||
| **E-Mail und Passwort** | Beim Erststart, und wenn authentik gerade nicht erreichbar ist. |
|
||||
| **Terminal im Gerätehaus** | Am Wandtablet, mit PIN oder Ausweiskarte. Eigene Oberfläche, kommt in Phase 4. |
|
||||
|
||||
Beim allerersten Start gibt es genau ein Konto: das aus `SEED_ADMIN_EMAIL` und
|
||||
`SEED_ADMIN_PASSWORT` in der `.env`. Vorgabe:
|
||||
|
||||
```
|
||||
admin@beispielwehr.de
|
||||
Feuerwehr2026
|
||||
```
|
||||
|
||||
**Dieses Passwort gehört nach dem ersten Login geändert.**
|
||||
|
||||
### Wenn die Anmeldung nicht klappt
|
||||
|
||||
Nach mehreren Fehlversuchen wird das Konto vorübergehend gesperrt — voreingestellt
|
||||
nach fünf Versuchen für fünf Minuten (`KIOSK_MAX_FEHLVERSUCHE`,
|
||||
`KIOSK_SPERRDAUER`). Die Meldung nennt nicht, ob die E-Mail-Adresse oder das
|
||||
Passwort falsch war; das ist Absicht und verhindert, dass sich vorhandene
|
||||
Konten erraten lassen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Die Übersicht
|
||||
|
||||
Nach der Anmeldung steht die Übersicht der eigenen Wehr.
|
||||
|
||||

|
||||
|
||||
Von oben nach unten:
|
||||
|
||||
**Kopfzeile.** Name der Wehr und darunter, wer angemeldet ist und mit welchen
|
||||
Rollen. Wer mehrere Rollen trägt — der Gerätewart ist oft auch Ausbilder —
|
||||
sieht alle.
|
||||
|
||||
**Sicherheitshinweis.** Der gelbe Kasten bleibt stehen, solange die Wehr die
|
||||
Anwendung nicht ausdrücklich freigegeben hat. Er ist keine Formalität:
|
||||
Atemschutzüberwachung und Alarmmonitor sind sicherheitsrelevant, und bis zur
|
||||
Freigabe bleibt die analoge Überwachung führend.
|
||||
|
||||
**Kennzahlen.** Vier Kacheln mit aktiven Mitgliedern, Fahrzeugen, Geräten und
|
||||
fälligen Prüfungen. Sie zählen nur die eigene Wehr — was eine andere Wehr auf
|
||||
derselben Installation hat, ist nicht sichtbar und nicht abfragbar.
|
||||
|
||||
**Stand des Aufbaus.** Welche Phasen fertig sind und was als Nächstes kommt.
|
||||
|
||||
### Die Seitenleiste
|
||||
|
||||
Links stehen alle Module. Was noch nicht existiert, ist ausgegraut und trägt
|
||||
rechts sein Phasenkürzel — `P2` heißt „kommt in Phase 2".
|
||||
|
||||
Was jemand überhaupt sieht, hängt an seinen Rechten. Ein einfaches Mitglied
|
||||
sieht die Mitgliederliste, aber nicht das Protokoll. Wichtig zum Verständnis:
|
||||
das Ausblenden ist nur Bequemlichkeit. Der eigentliche Schutz sitzt auf dem
|
||||
Server — wer eine Adresse direkt aufruft, für die ihm das Recht fehlt, bekommt
|
||||
trotzdem nichts.
|
||||
|
||||
---
|
||||
|
||||
## 3. Helle und dunkle Darstellung
|
||||
|
||||
Oben rechts wird zwischen hell und dunkel umgeschaltet. Voreingestellt ist die
|
||||
Einstellung des Betriebssystems.
|
||||
|
||||

|
||||
|
||||
Die dunkle Darstellung ist bewusst kein invertiertes Schwarz, sondern ein
|
||||
warmes Grau. Wer abends vom Fahrzeug hereinkommt und ans Terminal tritt, soll
|
||||
nicht geblendet werden.
|
||||
|
||||
---
|
||||
|
||||
## 4. 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
|
||||
kann sie also nachträglich zuschneiden, ohne dass jemand Code ändert.
|
||||
|
||||
| Rolle | Darf im Kern |
|
||||
|---|---|
|
||||
| **Administrator** | alles, auch über Wehren hinweg |
|
||||
| **Kommandant** | alles innerhalb der eigenen Wehr, einschließlich Rollen vergeben und Protokoll einsehen |
|
||||
| **Gerätewart** | Fahrzeuge und Geräte anlegen, ändern, aussondern; Prüfungen planen und durchführen |
|
||||
| **Atemschutzgerätewart** | zusätzlich Atemschutzgeräte, Träger und die Überwachung nach FwDV 7 |
|
||||
| **Kassier** | Buchungen und Kassenabschluss (Modul noch nicht geplant, siehe Roadmap) |
|
||||
| **Schriftführer** | Mitglieder pflegen, Einsätze und Berichte erfassen |
|
||||
| **Ausbilder** | Übungen planen, Teilnahme und Qualifikationen erfassen |
|
||||
| **Mitglied** | eigene Daten, Listen lesen |
|
||||
| **Terminal** | nur anmelden und erfassen — nie verwalten |
|
||||
|
||||
Zwei Dinge sind bewusst so gebaut:
|
||||
|
||||
**Der Gerätewart kommt nicht an die Kasse, der Kassier nicht an die Geräte.**
|
||||
Trennung nach Verantwortung, nicht nach Bequemlichkeit.
|
||||
|
||||
**Das Terminal darf fast nichts.** Wer am Wandtablet arbeitet, meldet sich
|
||||
zusätzlich mit PIN oder Karte an und handelt dann unter seiner eigenen Person.
|
||||
Das Terminal selbst ist nur die Tür.
|
||||
|
||||
---
|
||||
|
||||
## 5. Anmeldung über authentik einrichten
|
||||
|
||||
authentik läuft **getrennt** von FLORIAN und ist nicht Teil dieses Stacks.
|
||||
|
||||
### In authentik
|
||||
|
||||
1. Neuen Provider anlegen, Typ **OAuth2/OpenID Provider**
|
||||
2. Client-Typ: **Confidential**
|
||||
3. Redirect-URI genau so eintragen:
|
||||
```
|
||||
https://<ihre-florian-adresse>/api/auth/callback/authentik
|
||||
```
|
||||
4. Scopes: `openid`, `profile`, `email`
|
||||
5. Für die Rollenvorbelegung zusätzlich ein Scope-Mapping, das die Gruppen des
|
||||
Benutzers als Claim `groups` ausliefert
|
||||
6. Eine Application anlegen und mit dem Provider verbinden. Der Slug der
|
||||
Application ist der letzte Teil der Issuer-URL.
|
||||
|
||||
### In der `.env`
|
||||
|
||||
```bash
|
||||
AUTH_OIDC_ENABLED=true
|
||||
AUTH_OIDC_ISSUER=https://auth.beispiel.de/application/o/florian/
|
||||
AUTH_OIDC_CLIENT_ID=<aus authentik>
|
||||
AUTH_OIDC_CLIENT_SECRET=<aus authentik>
|
||||
AUTH_OIDC_GROUP_CLAIM=groups
|
||||
AUTH_OIDC_GROUP_MAPPING=feuerwehr-kommandant:KOMMANDANT,feuerwehr-geraetewart:GERAETEWART
|
||||
AUTH_OIDC_AUTO_PROVISION=true
|
||||
AUTH_OIDC_STANDARD_ORGANISATION=ff-musterhausen
|
||||
```
|
||||
|
||||
Danach die Anwendung neu starten. Auf der Anmeldeseite erscheint der Knopf für
|
||||
authentik.
|
||||
|
||||
### Wie die Rollenvorbelegung wirkt
|
||||
|
||||
Die Zuordnung greift **nur beim allerersten Login** einer Person. Danach ist
|
||||
die in FLORIAN gepflegte Rolle führend, und authentik überschreibt sie nicht
|
||||
mehr. Wer also einem Mitglied in FLORIAN die Rolle „Gerätewart" gibt, behält
|
||||
sie — auch wenn die authentik-Gruppe etwas anderes sagt.
|
||||
|
||||
Steht `AUTH_OIDC_AUTO_PROVISION=false`, muss die Person vorher in FLORIAN
|
||||
angelegt sein. Verknüpft wird über die E-Mail-Adresse.
|
||||
|
||||
### Wenn authentik ausfällt
|
||||
|
||||
Solange `AUTH_LOCAL_ENABLED=true` steht, bleibt der Passwort-Login als
|
||||
Rückfallweg erreichbar. Für den Wirkbetrieb ist es sinnvoll, mindestens ein
|
||||
Konto mit Passwort zu behalten — sonst sperrt ein Ausfall von authentik die
|
||||
ganze Wehr aus.
|
||||
|
||||
---
|
||||
|
||||
## 6. Terminal-Anmeldung im Gerätehaus
|
||||
|
||||
Die Oberfläche dazu kommt in Phase 4; der Anmeldeweg steht bereits.
|
||||
|
||||
Am Terminal wird zweifach geprüft:
|
||||
|
||||
1. **Das Gerät** weist sich mit `KIOSK_DEVICE_SECRET` aus. Ohne diesen Wert
|
||||
nimmt der Server keine PIN und keine Karte an.
|
||||
2. **Die Person** meldet sich mit einer sechsstelligen PIN (zusammen mit ihrer
|
||||
Mitgliedsnummer) oder mit einer RFID-Karte an.
|
||||
|
||||
Beides wird nur als Hash gespeichert — weder die PIN noch die Kartenkennung
|
||||
liegen im Klartext in der Datenbank.
|
||||
|
||||
PINs, die sich zu leicht raten lassen, weist FLORIAN ab: lauter gleiche
|
||||
Ziffern und fortlaufende Folgen wie `123456`.
|
||||
|
||||
Aus dem Seed lässt sich das ausprobieren: Mitgliedsnummer `0001` (Andreas
|
||||
Huber), PIN `471203`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Was protokolliert wird
|
||||
|
||||
Jede schreibende Änderung erzeugt einen Eintrag mit Zeitpunkt, Person,
|
||||
betroffenem Datensatz und den geänderten Feldern im Vorher-Nachher-Vergleich.
|
||||
Auch jede Anmeldung — erfolgreich wie gescheitert.
|
||||
|
||||
Zwei Eigenschaften sind wichtig:
|
||||
|
||||
**Der Log lässt sich nicht ändern.** Nicht über die Oberfläche, nicht über die
|
||||
Datenbank, auch nicht vom Administrator. Die Datenbank selbst verhindert es.
|
||||
|
||||
**Er lässt sich nur im Aufbewahrungslauf kürzen.** Nach der eingestellten
|
||||
Frist (`AUDIT_AUFBEWAHRUNG_TAGE`, Vorgabe 10 Jahre) sichert der
|
||||
Hintergrundprozess die alten Einträge und entfernt sie danach. Das ist der
|
||||
einzige Weg, auf dem etwas aus dem Log verschwindet.
|
||||
|
||||
**Passwörter, PINs und Kartenkennungen stehen nie im Log** — sie werden vor
|
||||
dem Schreiben herausgenommen.
|
||||
|
||||
Einsehbar ist der Log mit dem Recht `system.auditlog`, das Kommandant und
|
||||
Administrator haben. Die Ansicht dazu kommt in Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## 8. Wenn etwas nicht geht
|
||||
|
||||
| Beobachtung | Wahrscheinliche Ursache | Was hilft |
|
||||
|---|---|---|
|
||||
| Die Anwendung startet nicht und meldet fehlende Variablen | `.env` unvollständig | Die Meldung nennt jede fehlende Variable. Erklärungen dazu stehen in `.env.example`. |
|
||||
| Anmeldung schlägt immer fehl, obwohl das Passwort stimmt | Konto nach Fehlversuchen gesperrt | Fünf Minuten warten, oder `gesperrtBis` in der Datenbank zurücksetzen |
|
||||
| Listen sind leer, obwohl Daten da sind | Kein Mandantenkontext gesetzt | Das ist der Schutzmechanismus. Der Zugriff muss über `fuerOrganisation()` laufen. |
|
||||
| Dateien lassen sich nicht hochladen | MinIO nicht erreichbar oder Bucket fehlt | `http://localhost:9011` öffnen; `docker compose logs minio-init` |
|
||||
| Der Knopf für authentik fehlt | Issuer, Client-ID oder Secret nicht gesetzt | Alle drei sind nötig, sonst wird der Weg ausgeblendet |
|
||||
| `docker compose up` startet Traefik mit | Override-Datei wurde nicht mitgeladen | In Produktion `-f docker-compose.yml` angeben |
|
||||
|
||||
Zustand aller Randsysteme auf einen Blick:
|
||||
|
||||
```
|
||||
http://localhost:3100/api/health
|
||||
```
|
||||
|
||||
Antwortet mit `200` und `"status": "ok"`, wenn Datenbank, Objektspeicher und
|
||||
Warteschlange erreichbar sind — sonst mit `503` und der Angabe, welcher Dienst
|
||||
fehlt.
|
||||
|
||||
---
|
||||
|
||||
## Bilder erneuern
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
npm run dev
|
||||
npm run screenshots
|
||||
```
|
||||
|
||||
Die Bilder entstehen in `docs/screenshots/`, jeweils in heller und dunkler
|
||||
Darstellung, und werden mit eingecheckt — die Anleitung soll auch dann lesbar
|
||||
sein, wenn gerade kein Stack läuft.
|
||||
@@ -0,0 +1,237 @@
|
||||
# Aufwandsschätzung
|
||||
|
||||
Schätzung je Phase, daneben der tatsächliche Aufwand aus dem
|
||||
[Devlog](DEVLOG.md). Stand: 3. September 2026.
|
||||
|
||||
## Wie hier gerechnet wird
|
||||
|
||||
Es stehen bewusst **zwei** Größen nebeneinander, weil sie Verschiedenes
|
||||
aussagen:
|
||||
|
||||
| Größe | Bedeutung |
|
||||
|---|---|
|
||||
| **Sitzungszeit** | Wie lange Claude tatsächlich gearbeitet hat. Das ist die Zahl, die im Devlog steht, und die Grundlage für die Planung der nächsten Sitzung. |
|
||||
| **Vergleichsaufwand** | Was eine einzelne Person mit diesem Stack für dasselbe Ergebnis gebraucht hätte, in Personentagen zu 8 Stunden. Das ist die Zahl, die gegenüber der Wehr etwas bedeutet — sie beantwortet „was wäre das sonst wert gewesen". |
|
||||
|
||||
Der Vergleichsaufwand ist eine **Schätzung**, keine Messung. Er unterstellt
|
||||
jemanden, der den Stack kennt, die Feuerwehr-Fachlichkeit aber erst klären
|
||||
muss — also Recherche zu DGUV-Fristen, FwDV 3 und FwDV 7 einschließt.
|
||||
|
||||
Faktoren, die in die Schätzung eingehen:
|
||||
|
||||
- **Fachlichkeit** — wie viel muss geklärt werden, bevor gebaut werden kann?
|
||||
- **Sicherheitsbezug** — Atemschutz und Prüfnachweise brauchen mehr Tests und
|
||||
mehr Sorgfalt als eine Adressliste.
|
||||
- **Fremdsysteme** — Gotenberg, Nominatim, tileserver-gl, Ollama, RFID-Leser.
|
||||
Jedes ist eine eigene Fehlerquelle.
|
||||
- **Offline und Echtzeit** — die teuersten Anforderungen im ganzen Projekt.
|
||||
|
||||
## Übersicht
|
||||
|
||||
| Phase | Inhalt | Vergleichsaufwand | Sitzungszeit geschätzt | Sitzungszeit tatsächlich |
|
||||
|---|---|---:|---:|---:|
|
||||
| 0 | Fundament | 12–16 PT | — | **1:00 h** |
|
||||
| 1 | Stammdaten | 10–14 PT | 2,5–3,5 h | offen |
|
||||
| 2 | Wartung und Prüfung | 18–24 PT | 4,5–6 h | offen |
|
||||
| 3 | Einsätze und Übungen | 12–16 PT | 3–4 h | offen |
|
||||
| 4 | Kiosk-PWA mit Offline | 16–22 PT | 4–5,5 h | offen |
|
||||
| 5 | Atemschutz (FwDV 7) | 14–18 PT | 3,5–4,5 h | offen |
|
||||
| 6 | Kartenmodule | 14–18 PT | 3,5–4,5 h | offen |
|
||||
| 7 | Benachrichtigungen und Termine | 10–13 PT | 2,5–3,5 h | offen |
|
||||
| 8 | Lokale KI | 9–12 PT | 2,5–3 h | offen |
|
||||
| | **Summe** | **115–153 PT** | **26–34 h** | |
|
||||
|
||||
115–153 Personentage entsprechen etwa **6 bis 8 Monaten Vollzeitarbeit** einer
|
||||
einzelnen Person. Zum Vergleich: eine kommerzielle Feuerwehrverwaltung kostet
|
||||
je nach Umfang 500 bis 2.500 Euro im Jahr, dauerhaft, mit den Daten beim
|
||||
Anbieter.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Fundament
|
||||
|
||||
**Vergleichsaufwand: 12–16 PT** · **Tatsächlich: 1:00 h Sitzungszeit**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Monorepo, Werkzeuge, TypeScript, Tailwind, shadcn | 1,5 |
|
||||
| Docker Compose, Dockerfile, Traefik, Healthchecks | 2,5 |
|
||||
| Prisma-Schema der Basismodelle | 2,0 |
|
||||
| Row Level Security samt Rollen- und Rechtekonzept in Postgres | 2,5 |
|
||||
| Auth.js mit OIDC, lokalem Login und Terminal-Anmeldung | 3,0 |
|
||||
| storage, audit, warteschlange, Integrationsschnittstellen | 2,0 |
|
||||
| Basis-Layout, Anmeldeseite, Übersicht, Dark Mode | 1,5 |
|
||||
| Seed, Vitest, Playwright, Dokumentation | 1,5 |
|
||||
|
||||
Das Teuerste war nicht der sichtbare Teil, sondern die Mandantentrennung. RLS
|
||||
so aufzusetzen, dass Migrationen, Seed und Anmeldung funktionieren und die
|
||||
Anwendung trotzdem nichts Fremdes sieht, verlangt zwei getrennte
|
||||
Datenbankzugänge und ein sauberes Verständnis davon, wann welcher greift. Wer
|
||||
das nachträglich einbaut, zahlt ein Vielfaches.
|
||||
|
||||
## Phase 1 — Stammdaten
|
||||
|
||||
**Vergleichsaufwand: 10–14 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Modelle: Standort, Geraeteraum, Geraetetyp, Qualifikation | 1,5 |
|
||||
| Fahrzeug und Geraet um DIN-Typisierung und Lebensdauer erweitern | 1,5 |
|
||||
| Tabellen mit Sortierung, Filter, Volltextsuche, Spaltenauswahl | 3,0 |
|
||||
| QR-Code je Gerät, Etikettenbogen als PDF (65 Etiketten, DIN A4) | 1,5 |
|
||||
| QR-Scan im Browser über die Kamera | 1,0 |
|
||||
| CSV-/Excel-Import mit Spaltenzuordnung und Vorschau | 2,5 |
|
||||
| Gerätehistorie aus dem Audit-Log | 1,0 |
|
||||
| Massenbearbeitung und Tastaturbedienung | 1,0 |
|
||||
|
||||
Der Import ist der Posten, der erfahrungsgemäß aus dem Ruder läuft. Jede Wehr
|
||||
bringt eine andere Excel-Datei mit, und die Vorschau vor dem Übernehmen ist
|
||||
kein Komfort, sondern die Bedingung dafür, dass die Wehr dem Ergebnis traut.
|
||||
|
||||
## Phase 2 — Wartung und Prüfung
|
||||
|
||||
**Vergleichsaufwand: 18–24 PT** — die aufwendigste Phase
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Modelle: Vorlage, Checklistenpunkt, Termin, Prüfung, Ergebnis, Mangel, Rückruf | 3,0 |
|
||||
| Fristen-Dashboard mit Gruppierungen | 2,5 |
|
||||
| Nächtlicher BullMQ-Job: Termine erzeugen, Status setzen, erinnern | 2,5 |
|
||||
| Prüfungs-Wizard mit Fotoupload je Checklistenpunkt | 3,5 |
|
||||
| Unterschrift auf Canvas, Touch und Maus | 1,5 |
|
||||
| PDF-Protokoll über Gotenberg mit Fotos und Signatur | 3,0 |
|
||||
| Unveränderlichkeit und Korrekturprotokolle mit Verweis | 1,5 |
|
||||
| Lebensdauerüberwachung und Aussonderungsvorschlag | 1,5 |
|
||||
| Rückrufe, externe Wartung, Mängel-Workflow | 2,5 |
|
||||
|
||||
Hier entsteht der eigentliche Wert: die Software wird zum Nachweis gegenüber
|
||||
der Unfallkasse. Das heißt aber auch, dass jeder Fehler teuer ist. Die
|
||||
Unveränderlichkeit abgeschlossener Protokolle ist der Kern — und der Grund,
|
||||
warum der Audit-Log schon in Phase 0 append-only ist.
|
||||
|
||||
## Phase 3 — Einsätze und Übungen
|
||||
|
||||
**Vergleichsaufwand: 12–16 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Modelle: Einsatz, Einsatzfahrzeug, Einsatzkraft, Material, Lagemeldung, Übung | 2,0 |
|
||||
| Schnellerfassung mit Nachtragen | 1,5 |
|
||||
| Geocoding über self-hosted Nominatim mit Rückfallweg | 1,5 |
|
||||
| Personalauswahl nach Verfügbarkeit und Qualifikation | 2,0 |
|
||||
| Stärkeberechnung nach FwDV 3 | 1,0 |
|
||||
| Einsatzbericht als PDF, Jahresexport für die Gemeinde | 2,0 |
|
||||
| Statistik: Kategorien, Eintreffzeiten, Beteiligung, Auslastung | 2,5 |
|
||||
| Externes Portal mit signiertem QR-Link, ohne Login, rate-limited | 2,0 |
|
||||
|
||||
Das externe Portal für Fremdwehren ist sicherheitsrelevant: ein Zugang ohne
|
||||
Anmeldung, der zeitlich begrenzt und mengenbegrenzt sein muss. Der Aufwand
|
||||
steckt weniger in der Funktion als in der Absicherung.
|
||||
|
||||
## Phase 4 — Kiosk-PWA
|
||||
|
||||
**Vergleichsaufwand: 16–22 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Eigenes Kiosk-Layout, 64px-Ziele, hoher Kontrast, kein Hover-Zwang | 2,0 |
|
||||
| Anmeldung per RFID (WebHID), PIN und QR vom Handy | 2,5 |
|
||||
| Wizards: Wartung, Fahrtenbuch, Mangel, Inventur, Wäsche, Anwesenheit | 4,0 |
|
||||
| Hallen-Monitor mit DWD-Wetterwarnung | 2,0 |
|
||||
| Service Worker, App-Shell und Stammdaten offline | 3,0 |
|
||||
| IndexedDB-Outbox mit Idempotenzschlüssel und Synchronisation | 4,0 |
|
||||
| Konfliktbehandlung und Konfliktliste für den Administrator | 2,0 |
|
||||
| Playwright-Tests für den Offline-Fall | 1,5 |
|
||||
|
||||
Offline-Fähigkeit mit schreibenden Aktionen ist die technisch schwierigste
|
||||
Anforderung des ganzen Projekts. Eine Outbox ist schnell gebaut; dass sie
|
||||
unter Verbindungsabbruch, Doppelklick und parallelem Zugriff von zwei
|
||||
Terminals nichts verliert und nichts doppelt schreibt, ist die eigentliche
|
||||
Arbeit. Die Idempotenzschlüssel sind dafür der Schlüssel, nicht die Synchronisation.
|
||||
|
||||
## Phase 5 — Atemschutz nach FwDV 7
|
||||
|
||||
**Vergleichsaufwand: 14–18 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Modelle: Atemschutzeinsatz, Trupp, Druckmessung, G26, Belastungsübung | 2,0 |
|
||||
| Truppanlage per RFID, Eingangsdruck | 1,5 |
|
||||
| Serverseitig autoritative Timer, Socket.IO-Verteilung | 3,5 |
|
||||
| Rückzugsdruck-Berechnung, Ampel, akustischer Alarm | 2,0 |
|
||||
| Erinnerung zur Druckabfrage im Intervall | 1,0 |
|
||||
| Mayday-Knopf mit Protokolleintrag | 1,0 |
|
||||
| Lückenloses PDF-Protokoll am Einsatz | 1,5 |
|
||||
| G26.3-Fristen, Belastungsübungen, Tauglichkeitsampel | 2,0 |
|
||||
| Tests: Reload und Verbindungsabbruch überstehen | 2,0 |
|
||||
|
||||
Der Aufwand liegt nicht in der Funktion, sondern im Beweis, dass sie hält. Ein
|
||||
Timer, der einen Reload überlebt, ist Pflicht — deshalb muss die Zeit
|
||||
serverseitig geführt werden und der Browser nur anzeigen. Diese Phase bekommt
|
||||
den höchsten Testanteil im Projekt.
|
||||
|
||||
## Phase 6 — Kartenmodule
|
||||
|
||||
**Vergleichsaufwand: 14–18 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| MapLibre GL, tileserver-gl, MBTiles fürs Ausrückegebiet | 2,5 |
|
||||
| Hydranten mit PostGIS, Cluster, Filter, Umkreissuche | 3,0 |
|
||||
| Import aus GeoJSON und Stadtwerke-CSV | 1,5 |
|
||||
| Objekte und Feuerwehrpläne nach DIN 14095, Volltextsuche | 3,0 |
|
||||
| Lagekarte mit taktischen Zeichen nach FwDV 102 als SVG-Set | 3,5 |
|
||||
| Echtzeit-Sync der Lagekarte zwischen Geräten | 2,0 |
|
||||
| Wasserförderung: Höhenprofil, Druckverlust, Verstärkerpumpen | 2,5 |
|
||||
|
||||
Das SVG-Set für die taktischen Zeichen ist Fleißarbeit, aber unvermeidbar —
|
||||
fertige Sets sind selten frei verwendbar. Die Wasserförderungsberechnung ist
|
||||
Physik mit klaren Formeln und deshalb gut testbar.
|
||||
|
||||
## Phase 7 — Benachrichtigungen und Termine
|
||||
|
||||
**Vergleichsaufwand: 10–13 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| Kanalabstraktion: E-Mail, Telegram, ntfy, Web-Push | 2,5 |
|
||||
| Vorlagen mit Platzhaltern, je Ereignis und Rolle | 2,0 |
|
||||
| Abonnements je Person | 1,0 |
|
||||
| Kalender mit Serienterminen und Selbstanmeldung | 3,0 |
|
||||
| CalDAV-/ICS-Feed je Person mit Token | 1,5 |
|
||||
| Digitaler Dienstausweis mit signiertem QR und Prüfseite | 2,0 |
|
||||
|
||||
Das Grundgerüst der Kanalabstraktion steht bereits aus Phase 0
|
||||
(`lib/integrations/`), das spart hier etwa einen Personentag.
|
||||
|
||||
## Phase 8 — Lokale KI
|
||||
|
||||
**Vergleichsaufwand: 9–12 PT**
|
||||
|
||||
| Baustein | PT |
|
||||
|---|---:|
|
||||
| `lib/ai/` mit OpenAI-kompatiblem Client, abschaltbar | 1,0 |
|
||||
| Diktat: Aufnahme im Browser, faster-whisper, Transkript | 2,5 |
|
||||
| Berichtsassistent mit Übernehmen/Verwerfen | 2,0 |
|
||||
| Wiki mit pgvector, semantische Suche, RAG mit Quellenangabe | 3,5 |
|
||||
| Belegerfassung über ein Vision-Modell, immer bestätigen | 2,0 |
|
||||
|
||||
Der Client aus Phase 0 (`lib/integrations/llm.ts`) ist bereits vorhanden. Die
|
||||
Kennzeichnungspflicht — jede KI-Ausgabe ist als solche erkennbar und muss
|
||||
bestätigt werden, bevor sie in ein Dokument mit Nachweischarakter gelangt —
|
||||
kostet wenig Zeit, ist aber nicht verhandelbar.
|
||||
|
||||
---
|
||||
|
||||
## Was hier nicht drin ist
|
||||
|
||||
**Kassenmodul.** Die Rolle `KASSIER` ist in Phase 0 gefordert und angelegt,
|
||||
die Rechte `kasse.lesen`, `kasse.bearbeiten` und `kasse.abschluss` stehen
|
||||
bereit. Ein Kassenmodul kommt in keiner der Phasen 0 bis 8 vor. Geschätzter
|
||||
Vergleichsaufwand, falls gewünscht: **8–11 PT** für Buchungen, Beiträge,
|
||||
Rechnungen, Kassenabschluss und Jahresauswertung. Siehe
|
||||
[ROADMAP.md](ROADMAP.md).
|
||||
|
||||
**Betrieb und Einführung.** Datenübernahme aus der Altsoftware, Schulung der
|
||||
Wehr, Sicherungskonzept, Monitoring: nach Erfahrung noch einmal 15 bis 25
|
||||
Prozent des Entwicklungsaufwands.
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# Devlog
|
||||
|
||||
Entwicklungstagebuch mit Zeiterfassung. Jeder Arbeitsabschnitt bekommt einen
|
||||
Eintrag: wann, wie lange, was entstanden ist, und was dabei schiefging.
|
||||
|
||||
Die aufgewendete Zeit wandert nach jedem Abschnitt in die Spalte
|
||||
„tatsächlich" der [Aufwandsschätzung](AUFWANDSSCHAETZUNG.md).
|
||||
|
||||
## Zeitübersicht
|
||||
|
||||
| Phase | Sitzungszeit | Sitzungen | abgeschlossen |
|
||||
|---|---:|---:|---|
|
||||
| Phase 0 — Fundament | 1:00 h | 1 | 03.09.2026 |
|
||||
| **Summe** | **1:00 h** | **1** | |
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-02 / 03 · Phase 0 — Fundament
|
||||
|
||||
**23:13 – 00:13 · 1:00 h · Version 0.1.0**
|
||||
|
||||
### Was entstanden ist
|
||||
|
||||
46 Quelldateien, rund 6.100 Zeilen. Im Einzelnen:
|
||||
|
||||
| Bereich | Inhalt |
|
||||
|---|---|
|
||||
| Betrieb | `docker-compose.yml` (7 Dienste), Entwicklungs-Override, mehrstufiges Dockerfile, Postgres-Initialisierung, `.env.example` mit Kommentar zu jeder Variable |
|
||||
| Datenmodell | Prisma-Schema mit 11 Tabellen, 3 Migrationen |
|
||||
| Kern | `env`, `db`, `authz`, `storage`, `audit`, `auth`, `passwort`, `warteschlange`, 3 Integrationsschnittstellen |
|
||||
| Server | tRPC v11 mit drei Prozedurarten, Kontext, Wurzel-Router |
|
||||
| Oberfläche | Layout, Seitenleiste, Anmeldeseite, Übersicht, 5 shadcn-Bausteine, Farbwelt in OKLCH |
|
||||
| Daten | Seed: 1 Wehr, 9 Rollen, 38 Rechte, 16 Personen, 3 Fahrzeuge, 40 Geräte |
|
||||
| Tests | 32 Vitest-Tests, 10 Playwright-Tests |
|
||||
| Doku | README, Anleitung, Roadmap, Aufwandsschätzung, Versionierung, Changelog |
|
||||
|
||||
### Entscheidungen
|
||||
|
||||
**Zwei Datenbankverbindungen statt einer.** Der erste Entwurf hatte einen
|
||||
einzigen Prisma-Client. Der ist beim ersten Anmeldeversuch aufgelaufen: die
|
||||
Anwendung verbindet sich als `florian_app`, für den Row Level Security greift
|
||||
— und die Anmeldung muss eine E-Mail-Adresse über alle Wehren hinweg suchen,
|
||||
bevor überhaupt feststeht, zu welcher Wehr sie gehört. Ohne gesetzten
|
||||
Mandantenkontext liefert RLS null Zeilen, also fand der Login nie jemanden.
|
||||
|
||||
Die Lösung ist eine saubere Trennung: `prismaApp` für alle Fachdaten (RLS
|
||||
aktiv), `prismaSystem` für die wenigen Vorgänge, die vor der
|
||||
Mandantenauflösung stattfinden. Der Name ist bewusst unbequem, damit auffällt,
|
||||
wenn ihn jemand im falschen Zusammenhang benutzt.
|
||||
|
||||
**Kein FORCE ROW LEVEL SECURITY.** Der Tabelleneigentümer umgeht RLS. Das ist
|
||||
gewollt: Migrationen und Seed müssen über alle Mandanten hinweg arbeiten
|
||||
können. Mit `FORCE` wäre der Seed nicht mehr lauffähig gewesen.
|
||||
|
||||
**Audit-Log: append-only mit genau einer Ausnahme.** Der erste Trigger hat
|
||||
`UPDATE` und `DELETE` ausnahmslos gesperrt. Das ist beim Aufräumen der
|
||||
Testdaten aufgefallen — eine Organisation ließ sich nicht mehr löschen, weil
|
||||
die Kaskade auf den Audit-Log traf.
|
||||
|
||||
Das ist kein Testproblem, sondern ein echtes: nach
|
||||
`AUDIT_AUFBEWAHRUNG_TAGE` muss der Worker alte Einträge exportieren und dann
|
||||
entfernen, sonst wächst die Tabelle unbegrenzt und das Löschkonzept nach DSGVO
|
||||
lässt sich nicht umsetzen. Die zweite Migration führt deshalb einen
|
||||
ausdrücklichen Schalter ein: `SET LOCAL app.audit_bereinigung = 'an'` erlaubt
|
||||
`DELETE` für die laufende Transaktion. `UPDATE` bleibt unter allen Umständen
|
||||
gesperrt — ein Eintrag darf verschwinden, nachdem er gesichert wurde, aber
|
||||
niemals verfälscht werden.
|
||||
|
||||
**Rollen aus authentik nur als Vorbelegung.** Auf Nachfrage entschieden: der
|
||||
Gruppen-Claim setzt die Rolle beim ersten Login, danach ist die in FLORIAN
|
||||
gepflegte Rolle führend. Umgesetzt über das Feld `ausOidcGruppe` an
|
||||
`PersonRolle` und eine Prüfung, die nur bei leerer Rollenliste greift.
|
||||
|
||||
**Terminal mit zwei Schranken.** Erst weist sich das Gerät mit
|
||||
`KIOSK_DEVICE_SECRET` aus, dann die Person mit PIN oder Karte. Eine erratene
|
||||
sechsstellige PIN nützt ohne das Gerätegeheimnis nichts.
|
||||
|
||||
### Wo es gehakt hat
|
||||
|
||||
| Problem | Ursache | Lösung |
|
||||
|---|---|---|
|
||||
| Postgres startete nicht | `de_DE.UTF-8` gibt es im Debian-Basisimage nicht | ICU-Locale: `--locale-provider=icu --icu-locale=de-DE --locale=C.UTF-8` |
|
||||
| Init-Skript brach mit Syntaxfehler ab | psql ersetzt seine Variablen nicht innerhalb dollar-quotierter Blöcke | `DO $$`-Block durch `SELECT format(...) \gexec` ersetzt |
|
||||
| Anmeldung schlug immer fehl | siehe oben: RLS blockierte die E-Mail-Suche | zweite Datenbankverbindung |
|
||||
| `npm install` ließ Installationsskripte aus | npm 11.17 verlangt eine ausdrückliche Freigabe | `npm approve-scripts` für Prisma, esbuild und drei weitere |
|
||||
| Aufräumen der Testdaten scheiterte | Audit-Trigger blockierte die Kaskade | Aufbewahrungsschalter (siehe oben) |
|
||||
| Zwei E2E-Tests rot | Next.js hängt einen eigenen Routen-Ansager mit `role="alert"` in die Seite; `CardTitle` ist ein `div`, keine Überschrift | Fehlermeldung über `data-testid` angesprochen, Anmeldetitel zu einer echten `h1` gemacht |
|
||||
|
||||
### Abweichung von der Vorgabe
|
||||
|
||||
**`Berechtigung` trägt kein `organisationId`.** Die Architekturregel verlangt
|
||||
es für jede Tabelle. Diese Tabelle ist aber kein Mandantendatum, sondern der
|
||||
systemweite Katalog dessen, was die Software überhaupt kann — für alle Wehren
|
||||
identisch. Mandantenbezogen wird es erst über `RolleBerechtigung`, und die
|
||||
trägt `organisationId`. Die Tabelle ist für alle lesbar und für die
|
||||
Anwendungsrolle nicht beschreibbar. Die Ausnahme steht im Schema und in der
|
||||
Migration kommentiert.
|
||||
|
||||
**Phasenzuordnung in der Seitenleiste korrigiert.** Beim Schreiben der Roadmap
|
||||
ist aufgefallen, dass ich die Module den falschen Phasen zugeordnet hatte
|
||||
(Atemschutz als Phase 3, Einsätze als Phase 4). Nach `Phase-3.md` bis
|
||||
`Phase-8.md` gilt: 3 Einsätze, 4 Kiosk, 5 Atemschutz, 6 Karten, 7
|
||||
Benachrichtigungen, 8 KI. Seitenleiste, Kommentare in `authz.ts` und die
|
||||
Übersicht sind entsprechend berichtigt.
|
||||
|
||||
**Kassenmodul kommt in keiner Phase vor.** Die Rolle `KASSIER` ist gefordert
|
||||
und angelegt, ein Modul dazu gibt es in `Phase-0.md` bis `Phase-8.md` nicht.
|
||||
Die Rechte stehen im Katalog, es gibt aber keinen Navigationseintrag. Offene
|
||||
Entscheidung, siehe [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
### Nachweise
|
||||
|
||||
- `npm run typecheck` — ohne Ausgabe
|
||||
- `npm run test` — 32 Tests grün, davon 8 gegen eine echte Datenbank
|
||||
- `npm run test:e2e` — 10 Tests grün (Desktop und Tablet)
|
||||
- `npm run build` — 6 Routen, 103 kB gemeinsames JavaScript
|
||||
- `docker compose up -d` — Postgres, Redis, MinIO, Gotenberg gesund
|
||||
|
||||
---
|
||||
|
||||
## Vorlage für neue Einträge
|
||||
|
||||
```markdown
|
||||
## JJJJ-MM-TT · Phase N — Titel
|
||||
|
||||
**HH:MM – HH:MM · X:XX h · Version 0.N.0**
|
||||
|
||||
### Was entstanden ist
|
||||
### Entscheidungen
|
||||
### Wo es gehakt hat
|
||||
### Abweichung von der Vorgabe
|
||||
### Nachweise
|
||||
```
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
# Roadmap
|
||||
|
||||
Was FLORIAN wann kann. Die Phasen kommen aus `Phase-0.md` bis `Phase-8.md` und
|
||||
werden **einzeln** abgearbeitet — nie zwei in einem Schritt.
|
||||
|
||||
Aufwände je Phase: [AUFWANDSSCHAETZUNG.md](AUFWANDSSCHAETZUNG.md).
|
||||
Versionszuordnung: [VERSIONIERUNG.md](VERSIONIERUNG.md).
|
||||
|
||||
## Stand
|
||||
|
||||
```
|
||||
Phase 0 ████████████████████ fertig 0.1.0
|
||||
Phase 1 ░░░░░░░░░░░░░░░░░░░░ offen 0.2.0
|
||||
Phase 2 ░░░░░░░░░░░░░░░░░░░░ offen 0.3.0
|
||||
Phase 3 ░░░░░░░░░░░░░░░░░░░░ offen 0.4.0
|
||||
Phase 4 ░░░░░░░░░░░░░░░░░░░░ offen 0.5.0
|
||||
Phase 5 ░░░░░░░░░░░░░░░░░░░░ offen 0.6.0
|
||||
Phase 6 ░░░░░░░░░░░░░░░░░░░░ offen 0.7.0
|
||||
Phase 7 ░░░░░░░░░░░░░░░░░░░░ offen 0.8.0
|
||||
Phase 8 ░░░░░░░░░░░░░░░░░░░░ offen 0.9.0
|
||||
Freigabe ░░░░░░░░░░░░░░░░░░░░ offen 1.0.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Fundament · fertig
|
||||
|
||||
Alles, worauf der Rest aufsetzt.
|
||||
|
||||
- Monorepo, Docker Compose mit Traefik, PostgreSQL 17 + PostGIS, Redis, MinIO,
|
||||
Gotenberg
|
||||
- Mandantentrennung über Row Level Security, geprüft gegen eine echte Datenbank
|
||||
- Append-only-Audit-Log mit kontrolliertem Aufbewahrungslauf
|
||||
- Anmeldung: OIDC gegen authentik, lokaler Passwort-Login, Terminal per
|
||||
PIN und RFID
|
||||
- 9 Rollen, 38 Einzelrechte, zentral in `lib/authz.ts`
|
||||
- Dateiablage in MinIO, Zugriff nur über `lib/storage.ts`
|
||||
- Basis-Layout mit Seitenleiste und dunkler Darstellung
|
||||
- Seed mit einer vollständigen Beispielwehr
|
||||
|
||||
## Phase 1 — Stammdaten · als Nächstes
|
||||
|
||||
Die Wehr trägt ein, was sie hat.
|
||||
|
||||
- Standort, Geraeteraum, Geraetetyp, Qualifikationstyp, Qualifikation
|
||||
- Fahrzeug um Fahrgestellnummer, DIN-Typ und TÜV-Termin erweitern
|
||||
- Geraet um Lebensdauerkategorie und die Zuordnung
|
||||
„Standort ODER Fahrzeug + Geräteraum" erweitern
|
||||
- Tabellen mit Sortierung, Filter, Volltextsuche und Spaltenauswahl
|
||||
- QR-Code je Gerät, Etikettenbogen als PDF (DIN A4, 65 Etiketten)
|
||||
- QR-Scan über die Kamera öffnet direkt die Gerätedetailseite
|
||||
- CSV-/Excel-Import mit Spaltenzuordnung und Vorschau
|
||||
- Gerätehistorie aus dem Audit-Log
|
||||
- Qualifikationen mit Ablaufwarnung auf der Übersicht
|
||||
|
||||
**Danach kann die Wehr:** ihren Bestand vollständig führen und jedes Gerät per
|
||||
QR-Code am Regal wiederfinden.
|
||||
|
||||
## Phase 2 — Wartung und Prüfung
|
||||
|
||||
Das wichtigste Modul. Ab hier ersetzt FLORIAN den Ordner.
|
||||
|
||||
- Wartungsvorlagen mit Checklisten, Intervall und Vorwarnzeit
|
||||
- Fristen-Dashboard: überfällig / diese Woche / diesen Monat / nächste 90 Tage
|
||||
- Nächtlicher Job erzeugt Termine, setzt Status, erinnert bei T-30, T-7 und T-0
|
||||
- Prüfungs-Wizard mit Foto je Checklistenpunkt, Zeiterfassung und Unterschrift
|
||||
- PDF-Prüfprotokoll über Gotenberg, unveränderlich in MinIO
|
||||
- Lebensdauerüberwachung mit Aussonderungsvorschlag
|
||||
- Rückrufe markieren betroffene Geräte automatisch
|
||||
- Mängel-Workflow: melden → zugewiesen → in Arbeit → behoben
|
||||
|
||||
**Danach kann die Wehr:** einen DGUV-Nachweis führen, der einer Prüfung durch
|
||||
die Unfallkasse standhält.
|
||||
|
||||
## Phase 3 — Einsätze und Übungen
|
||||
|
||||
- Einsatz mit allen Zeitstempeln: alarmiert, ausgerückt, eingetroffen,
|
||||
Einsatzende, eingerückt, einsatzbereit
|
||||
- Schnellerfassung: Stichwort, Adresse, Fahrzeuge — Rest später
|
||||
- Geocoding über self-hosted Nominatim
|
||||
- Personalauswahl mit Vorschlag nach Verfügbarkeit und Qualifikation
|
||||
- Stärkeberechnung nach FwDV 3
|
||||
- Einsatzbericht als PDF, Jahresexport für die Gemeinde
|
||||
- Statistik: Kategorien, Eintreffzeiten, Beteiligung, Fahrzeugauslastung
|
||||
- Externes Portal für Fremdwehren über signierten QR-Link, ohne Anmeldung
|
||||
|
||||
**Danach kann die Wehr:** ihre Jahresstatistik auf Knopfdruck erzeugen.
|
||||
|
||||
## Phase 4 — Kiosk-PWA
|
||||
|
||||
Für die Wandtablets in der Fahrzeughalle.
|
||||
|
||||
- Eigenes Layout: große Kacheln, mindestens 64px, mit Handschuhen bedienbar
|
||||
- Anmeldung per RFID, PIN oder QR vom Handy, Auto-Logout nach 90 Sekunden
|
||||
- Wizards: Wartung, Fahrtenbuch, Mangel melden, Inventur, Wäsche, Anwesenheit
|
||||
- Hallen-Monitor: Vollbild mit Terminen, überfälligen Prüfungen,
|
||||
Fahrzeugstatus, Wetterwarnung
|
||||
- Offline-Betrieb mit IndexedDB-Outbox und Synchronisation bei Reconnect
|
||||
|
||||
**Danach kann die Wehr:** einen Mangel in unter 30 Sekunden melden, ohne den
|
||||
Rechner im Büro einzuschalten — auch wenn das Netz gerade weg ist.
|
||||
|
||||
## Phase 5 — Atemschutz nach FwDV 7
|
||||
|
||||
Sicherheitsrelevant. Jede Ansicht zeigt dauerhaft den Hinweis, dass die
|
||||
analoge Überwachung führend bleibt — abschaltbar erst über eine Option, die
|
||||
eine dokumentierte Freigabe durch den Kommandanten voraussetzt.
|
||||
|
||||
- Truppanlage per RFID, Eingangsdruck erfassen
|
||||
- Serverseitig autoritative Timer, per Socket.IO verteilt
|
||||
- Rückzugsdruck-Berechnung mit Ampel und akustischem Alarm
|
||||
- Erinnerung zur Druckabfrage im konfigurierbaren Intervall
|
||||
- Mayday-Knopf je Trupp
|
||||
- Lückenloses PDF-Protokoll am Einsatz
|
||||
- G26.3-Fristen, Belastungsübungen, Tauglichkeitsübersicht
|
||||
|
||||
**Danach kann die Wehr:** die Überwachung digital mitführen — parallel zur
|
||||
analogen, bis die Wehr ausdrücklich anders entscheidet.
|
||||
|
||||
## Phase 6 — Kartenmodule
|
||||
|
||||
- MapLibre GL mit selbst gehosteten Kacheln fürs Ausrückegebiet
|
||||
- Hydranten mit Typ, Nennweite, Durchfluss, letzter Prüfung; Import aus
|
||||
GeoJSON und Stadtwerke-CSV
|
||||
- Objekte und Feuerwehrpläne nach DIN 14095, vom Einsatz aus erreichbar
|
||||
- Lagekarte mit taktischen Zeichen nach FwDV 102, Echtzeit-Sync
|
||||
- Wasserförderung: Druckverlust, Verstärkerpumpen, druckbares Schema
|
||||
|
||||
**Danach kann die Wehr:** auf der Anfahrt sehen, wo der nächste Hydrant liegt
|
||||
und was ihn hergibt.
|
||||
|
||||
## Phase 7 — Benachrichtigungen und Termine
|
||||
|
||||
- Kanäle: E-Mail, Telegram, ntfy, Web-Push. WhatsApp bewusst nicht.
|
||||
- Vorlagen mit Platzhaltern, je Ereignis und Rolle konfigurierbar
|
||||
- Kalender: Dienste, Übungen, Lehrgänge, Arbeitsdienste; Serientermine,
|
||||
Selbstanmeldung mit Teilnehmerlimit, Anwesenheitsliste zum Ausdrucken
|
||||
- CalDAV-/ICS-Feed je Person
|
||||
- Digitaler Dienstausweis mit signiertem QR und Prüfseite
|
||||
|
||||
### Warum kein WhatsApp
|
||||
|
||||
Die WhatsApp Business API kostet je Konversation und verlangt einen
|
||||
zugelassenen Anbieter dazwischen — laufende Kosten für eine Wehr, die keinen
|
||||
Etat dafür hat. Der schwerere Grund ist der Datenschutz: die Nummern aller
|
||||
Mitglieder gingen an Meta, was sich mit einer Anwendung, die bewusst im
|
||||
Gerätehaus steht, nicht verträgt. Telegram, ntfy und Web-Push decken denselben
|
||||
Bedarf ab, ntfy sogar vollständig selbst gehostet.
|
||||
|
||||
## Phase 8 — Lokale KI
|
||||
|
||||
Alles gegen Ollama, kein externer Anbieter, komplett abschaltbar.
|
||||
|
||||
- Diktat: Aufnahme im Browser → faster-whisper → Transkript im Berichtsfeld
|
||||
- Berichtsassistent: aus Stichpunkten einen Entwurf, immer als Vorschlag
|
||||
- Wiki mit pgvector: semantische Suche mit Quellenangabe
|
||||
- Belegerfassung: Foto einer Quittung → Vorschlag für Betrag, Datum, Händler
|
||||
|
||||
Jede KI-Ausgabe ist gekennzeichnet und muss bestätigt werden, bevor sie in ein
|
||||
Dokument mit Nachweischarakter gelangt.
|
||||
|
||||
## 1.0.0 — Freigabe
|
||||
|
||||
Kein neues Modul, sondern eine Entscheidung: die Wehr setzt FLORIAN im
|
||||
Echtbetrieb ein. Voraussetzungen:
|
||||
|
||||
- Datenübernahme aus der bisherigen Verwaltung abgeschlossen und abgeglichen
|
||||
- Sicherungskonzept steht und ist einmal wiederhergestellt worden
|
||||
- Schulung von Kommandant, Gerätewart und Atemschutzgerätewart erfolgt
|
||||
- Löschkonzept nach DSGVO abgenommen
|
||||
- Der Hinweis zur analogen Atemschutzüberwachung wird bewusst und
|
||||
dokumentiert abgeschaltet — oder bewusst stehen gelassen
|
||||
|
||||
---
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
### Kassenmodul fehlt in der Phasenplanung
|
||||
|
||||
Phase 0 verlangt die Rolle `KASSIER`, und sie ist angelegt — mit den Rechten
|
||||
`kasse.lesen`, `kasse.bearbeiten` und `kasse.abschluss`. Ein Kassenmodul kommt
|
||||
in keiner der Phasen 0 bis 8 vor. Die Rolle hätte also dauerhaft Rechte ohne
|
||||
zugehörige Oberfläche.
|
||||
|
||||
Zwei Wege:
|
||||
|
||||
1. **Phase 9 ergänzen** — Buchungen, Beiträge, Rechnungen, Kassenabschluss,
|
||||
Jahresauswertung. Geschätzt 8–11 PT.
|
||||
2. **Rolle belassen, Modul streichen** — der Kassier nutzt weiterhin seine
|
||||
bisherige Lösung, die Rechte bleiben als Platzhalter stehen.
|
||||
|
||||
Solange das nicht entschieden ist, bleiben die Rechte im Katalog, aber es gibt
|
||||
keinen Navigationseintrag dafür.
|
||||
|
||||
### Weitere Kandidaten für später
|
||||
|
||||
| Idee | Nutzen | grob |
|
||||
|---|---|---:|
|
||||
| Fahrtenbuch als eigenes Modul | derzeit nur als Kiosk-Wizard in Phase 4 vorgesehen | 3–4 PT |
|
||||
| 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.
|
||||
@@ -0,0 +1,117 @@
|
||||
# Versionierung
|
||||
|
||||
## Versionsnummern
|
||||
|
||||
FLORIAN folgt [Semantic Versioning](https://semver.org/lang/de/) in der Form
|
||||
`MAJOR.MINOR.PATCH`.
|
||||
|
||||
Solange die Hauptversion `0` ist, gilt die Software als im Aufbau: es gibt
|
||||
keine Zusage, dass eine Schnittstelle stabil bleibt.
|
||||
|
||||
| Stelle | wird erhöht, wenn |
|
||||
|---|---|
|
||||
| `MAJOR` | Bestehendes bricht — Datenmodell, REST-API unter `/api/v1` oder Konfiguration ändern sich so, dass eine bestehende Installation Handarbeit braucht. Ab `1.0.0`. |
|
||||
| `MINOR` | Eine Phase ist abgeschlossen oder ein Funktionsbereich kommt hinzu. |
|
||||
| `PATCH` | Fehlerbehebungen, Textkorrekturen, Abhängigkeits-Updates — alles ohne neue Funktion. |
|
||||
|
||||
### Zuordnung Phase zu Version
|
||||
|
||||
Jede abgeschlossene Phase bekommt eine Minor-Version. Das macht aus der
|
||||
Versionsnummer eine Aussage über den Funktionsumfang.
|
||||
|
||||
| Version | Phase |
|
||||
|---|---|
|
||||
| `0.1.0` | Phase 0 — Fundament |
|
||||
| `0.2.0` | Phase 1 — Stammdaten |
|
||||
| `0.3.0` | Phase 2 — Wartung und Prüfung |
|
||||
| `0.4.0` | Phase 3 — Einsätze und Übungen |
|
||||
| `0.5.0` | Phase 4 — Kiosk-PWA |
|
||||
| `0.6.0` | Phase 5 — Atemschutz |
|
||||
| `0.7.0` | Phase 6 — Kartenmodule |
|
||||
| `0.8.0` | Phase 7 — Benachrichtigungen und Termine |
|
||||
| `0.9.0` | Phase 8 — Lokale KI |
|
||||
| `1.0.0` | Freigabe für den Wirkbetrieb durch die Wehr |
|
||||
|
||||
`1.0.0` ist bewusst an die **Freigabe** gebunden, nicht an Phase 8. Erst wenn
|
||||
eine Wehr die Software im Echtbetrieb einsetzt, darf die analoge Überwachung
|
||||
zurücktreten — und erst dann ist eine Zusage über Stabilität ehrlich.
|
||||
|
||||
## Wo die Version steht
|
||||
|
||||
- `package.json` → `version` ist die Wahrheit.
|
||||
- `CHANGELOG.md` bekommt je Version einen eigenen Abschnitt.
|
||||
- Git-Tag in der Form `v0.1.0`.
|
||||
|
||||
```bash
|
||||
npm version minor -m "Version %s — Phase 1 abgeschlossen"
|
||||
git push --follow-tags
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
Format nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), auf
|
||||
Deutsch. Überschriften je Abschnitt:
|
||||
|
||||
- **Hinzugefügt** — neue Funktionen
|
||||
- **Geändert** — Verhalten, das anders geworden ist
|
||||
- **Veraltet** — bleibt vorerst, verschwindet aber
|
||||
- **Entfernt** — ist weg
|
||||
- **Behoben** — Fehlerbehebungen
|
||||
- **Sicherheit** — alles, was Sicherheitsbezug hat
|
||||
|
||||
Einträge beschreiben, was sich für die **Wehr** ändert, nicht welche Datei
|
||||
angefasst wurde. Also: „Prüfprotokolle lassen sich als PDF ablegen" statt
|
||||
„PdfService ergänzt".
|
||||
|
||||
Was noch nicht veröffentlicht ist, sammelt sich unter `[Unveröffentlicht]` und
|
||||
bekommt beim Setzen des Tags die Versionsnummer und das Datum.
|
||||
|
||||
## Branches
|
||||
|
||||
| Branch | Zweck |
|
||||
|---|---|
|
||||
| `main` | immer lauffähig. Tests grün, Build durch. |
|
||||
| `feat/<nr>-<kurz>` | neue Funktion, `<nr>` ist die Issue-Nummer |
|
||||
| `fix/<nr>-<kurz>` | Fehlerbehebung |
|
||||
| `chore/<kurz>` | Aufräumen, Abhängigkeiten, Werkzeuge |
|
||||
|
||||
Nie direkt auf `main` committen. Jede Änderung läuft über einen Branch und
|
||||
einen Pull Request.
|
||||
|
||||
## Commits
|
||||
|
||||
Deutsch, Imperativ, erste Zeile höchstens 72 Zeichen. Die letzte Zeile
|
||||
verweist auf das Issue:
|
||||
|
||||
```
|
||||
Prüfprotokoll als PDF über Gotenberg erzeugen
|
||||
|
||||
Das Protokoll wird nach dem Abschluss unveränderlich in MinIO abgelegt.
|
||||
Korrekturen erzeugen ein neues Protokoll mit Verweis auf das ursprüngliche.
|
||||
|
||||
Refs #42
|
||||
```
|
||||
|
||||
Der Text des Pull Requests enthält `Closes #42`.
|
||||
|
||||
## Datenbankmigrationen
|
||||
|
||||
- Eine Migration wird nach dem Anwenden **nie** verändert. Ein Fehler wird mit
|
||||
einer neuen Migration behoben.
|
||||
- Der Ordnername sagt, worum es geht: `20260902000100_rls_policies`.
|
||||
- Jede Migration beginnt mit einem Kommentarblock, der erklärt **warum** sie
|
||||
existiert — nicht nur was sie tut.
|
||||
- Migrationen, die Daten verlieren können, werden im Changelog unter
|
||||
**Sicherheit** oder **Geändert** ausdrücklich genannt.
|
||||
|
||||
## Was vor einem Tag erledigt sein muss
|
||||
|
||||
1. `npm run typecheck` ohne Ausgabe
|
||||
2. `npm run test` grün
|
||||
3. `npm run test:e2e` grün
|
||||
4. `npm run build` durch
|
||||
5. `docker compose -f docker-compose.yml up -d` startet sauber
|
||||
6. Changelog-Abschnitt geschrieben
|
||||
7. Devlog-Eintrag mit der aufgewendeten Zeit ergänzt
|
||||
8. Aufwandsschätzung um den Ist-Wert ergänzt
|
||||
9. Bildschirmfotos neu erzeugt, falls sich die Oberfläche geändert hat
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
Reference in New Issue
Block a user