Version 1.0.0 — Freigabe fuer den Echtbetrieb
Kein neues Modul. Zwoelf Phasen sind fertig, und die Wehr kann ab jetzt echte Daten hineingeben, ohne befuerchten zu muessen, sie beim naechsten Update zu verlieren. Neu dazu, weil beides zum Betrieb gehoert: update.sh aktualisiert auf einem Linux-Server in einem Befehl. Der Ablauf darf an jeder Stelle abbrechen, ohne eine halb aktualisierte Anlage zu hinterlassen: erst nachsehen ob es etwas Neues gibt, dann sichern, dann holen und bauen (schlaegt das fehl, laeuft die alte Fassung unveraendert weiter), dann umschalten, dann pruefen ob die Anwendung antwortet. Tut sie es nicht, stehen die Protokollzeilen da und die Befehle fuer den Rueckweg. docs/KURZANLEITUNG.md ist der Einstieg fuer die Wehr - nach Rollen sortiert, aufgabenorientiert statt nach Modulen, und bei jedem heiklen Punkt steht das Warum dabei. Die vollstaendige Anleitung hat 63 Kapitel; die liest niemand, bevor er das erste Geraet pruefen will. Die Roadmap haelt den Stand JEDER Freigabevoraussetzung fest, auch der offenen: Datenuebernahme entfaellt (die Wehr pflegt von Hand ein), Sicherungskonzept erledigt, Loeschkonzept technisch vollstaendig aber nicht abgenommen, Schulung steht aus, Atemschutzhinweis bewusst stehen gelassen. Eine Freigabe, die abhakt was nicht geprueft wurde, ist keine. Nebenbei die Ursache der Testflackerei gefunden, die sich durch mehrere Phasen zog: zwei Datenbanktests schalteten beim Aufraeumen ihre Trigger mit ALTER TABLE ... DISABLE TRIGGER ab - eine Schemaaenderung, die fuer die ganze Datenbank gilt. Vitest faehrt die Dateien parallel, also pruefte eine Datei gerade dass ein Trigger greift, waehrend eine andere ihn abgeraeumt hatte. Jetzt sitzungslokal ueber session_replication_role. Erstmals in diesem Projekt: fuenf Unit-Testlaeufe hintereinander 601 gruen und ein E2E-Lauf mit 268 gruen ohne Fehlschlag und ohne Flackern. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9156dcc144
commit
30faa6cb02
@@ -6,6 +6,84 @@ Alle nennenswerten Änderungen an FLORIAN. Format nach
|
|||||||
|
|
||||||
Die Regeln dazu stehen in [docs/VERSIONIERUNG.md](docs/VERSIONIERUNG.md).
|
Die Regeln dazu stehen in [docs/VERSIONIERUNG.md](docs/VERSIONIERUNG.md).
|
||||||
|
|
||||||
|
## [1.0.0] — 2026-09-06
|
||||||
|
|
||||||
|
**Freigabe für den Echtbetrieb.**
|
||||||
|
|
||||||
|
Kein neues Modul. Zwölf Phasen sind fertig, und die Wehr kann ab jetzt echte
|
||||||
|
Daten hineingeben, ohne befürchten zu müssen, sie beim nächsten Update zu
|
||||||
|
verlieren.
|
||||||
|
|
||||||
|
Was 1.0.0 bedeutet: der Funktionsumfang ist vollständig, die Datenbank ist
|
||||||
|
stabil, Migrationen sind aufwärtskompatibel, und ab hier gilt semantische
|
||||||
|
Versionierung — ein Update bricht nichts, ohne dass es in der Hauptnummer
|
||||||
|
steht.
|
||||||
|
|
||||||
|
Was es nicht bedeutet: dass keine Fehler mehr da sind.
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
|
||||||
|
- **`update.sh`** — Aktualisieren auf einem Linux-Server, in einem Befehl. Der
|
||||||
|
Ablauf darf an jeder Stelle abbrechen, ohne eine halb aktualisierte Anlage
|
||||||
|
zu hinterlassen: erst nachsehen, ob es überhaupt etwas Neues gibt, dann
|
||||||
|
**sichern**, dann holen und bauen (schlägt das fehl, läuft die alte Fassung
|
||||||
|
unverändert weiter), dann umschalten, dann nachsehen, ob die Anwendung
|
||||||
|
antwortet. Tut sie es nicht, stehen die letzten Protokollzeilen da und die
|
||||||
|
Befehle, mit denen der vorherige Stand zurückkommt.
|
||||||
|
|
||||||
|
Vorher prüft es Docker, git, die `.env` und dass keine unverbuchten
|
||||||
|
Änderungen im Verzeichnis liegen. `--pruefen` sieht nur nach,
|
||||||
|
`--ohne-sicherung` gibt es für Testinstallationen.
|
||||||
|
|
||||||
|
- **`docs/KURZANLEITUNG.md`** — der Einstieg, nach Rollen sortiert und auf ein
|
||||||
|
paar Seiten. Die vollständige Anleitung hat 63 Kapitel; die liest niemand,
|
||||||
|
bevor er das erste Gerät prüfen will. Diese hier ist aufgabenorientiert
|
||||||
|
(„ein Gerät prüfen", „einen Kontoauszug einlesen") statt nach Modulen
|
||||||
|
geordnet, und sie sagt bei jedem heiklen Punkt dazu, warum es so ist — warum
|
||||||
|
ein abgeschlossenes Prüfprotokoll nicht änderbar ist, warum gegengebucht und
|
||||||
|
nicht korrigiert wird, warum der Atemschutzhinweis stehen bleibt.
|
||||||
|
|
||||||
|
### Geändert
|
||||||
|
|
||||||
|
- Version auf **1.0.0**. Ab hier gilt semantische Versionierung nach
|
||||||
|
[docs/VERSIONIERUNG.md](docs/VERSIONIERUNG.md).
|
||||||
|
- Die Roadmap hält den Stand **jeder** Freigabevoraussetzung fest, auch der
|
||||||
|
offenen. Zwei sind organisatorisch und stehen aus: die Abnahme des
|
||||||
|
Löschkonzepts durch die Wehr und die Schulung. An der Software hängt nichts
|
||||||
|
davon ab; sie stehen dort, damit später nachlesbar ist, was zum Zeitpunkt
|
||||||
|
der Freigabe erledigt war und was nicht.
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
|
||||||
|
- **Die Testläufe flackerten seit Phase 8 — jetzt ist die Ursache weg.** Zwei
|
||||||
|
Datenbanktests schalteten beim Aufräumen ihre Trigger mit
|
||||||
|
`ALTER TABLE ... DISABLE TRIGGER` ab. Das ist eine Schemaänderung: sie gilt
|
||||||
|
für die ganze Datenbank und für alle. Vitest fährt die Testdateien parallel
|
||||||
|
— während die eine Datei abräumte, prüfte die andere gerade, dass genau
|
||||||
|
dieser Trigger greift, und sah keinen.
|
||||||
|
|
||||||
|
Das erklärt rückwirkend eine ganze Reihe von Fehlschlägen, die als
|
||||||
|
„Flackern" abgetan wurden und keines waren. Statt der Schemaänderung wird
|
||||||
|
jetzt `SET LOCAL session_replication_role = 'replica'` benutzt: dieselbe
|
||||||
|
Wirkung, aber nur in der eigenen Transaktion. Fünf Läufe hintereinander
|
||||||
|
sauber.
|
||||||
|
|
||||||
|
- **Ein Datenbanktest räumte nur am Ende auf.** Brach ein Lauf ab, scheiterte
|
||||||
|
der nächste an einer Eindeutigkeit, die mit dem Geprüften nichts zu tun
|
||||||
|
hatte. Er räumt jetzt auch am Anfang auf.
|
||||||
|
|
||||||
|
### Zum Stand der Freigabevoraussetzungen
|
||||||
|
|
||||||
|
| Voraussetzung | Stand |
|
||||||
|
|---|---|
|
||||||
|
| Datenübernahme | **entfällt** — die Wehr pflegt von Hand ein |
|
||||||
|
| Sicherungskonzept, einmal wiederhergestellt | **erledigt** (Phase 11) |
|
||||||
|
| Löschkonzept nach DSGVO | **technisch vollständig** (Phase 11), Abnahme steht aus |
|
||||||
|
| Schulung | **steht aus**, Grundlage ist die Kurzanleitung |
|
||||||
|
| Atemschutzhinweis | **bewusst stehen gelassen** — FLORIAN bleibt ergänzend |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.12.0] — 2026-09-05
|
## [0.12.0] — 2026-09-05
|
||||||
|
|
||||||
Phase 11: Freigabereife. Die drei Software-Voraussetzungen für 1.0.0 —
|
Phase 11: Freigabereife. Die drei Software-Voraussetzungen für 1.0.0 —
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
# FLORIAN — Feuerwehr-Verwaltungsplattform
|
# FLORIAN — Feuerwehr-Verwaltungsplattform
|
||||||
|
|
||||||
|
**Version 1.0.0** — freigegeben für den Echtbetrieb.
|
||||||
|
|
||||||
Selbstgehostete Verwaltungssoftware für Freiwillige Feuerwehren. Läuft im
|
Selbstgehostete Verwaltungssoftware für Freiwillige Feuerwehren. Läuft im
|
||||||
Gerätehaus, nicht in der Cloud. Keine Telemetrie, keine externen CDNs.
|
Gerätehaus, nicht in der Cloud. Keine Telemetrie, keine externen CDNs.
|
||||||
|
|
||||||
|
> **Wer FLORIAN benutzen und nicht bauen will:**
|
||||||
|
> [**Kurzanleitung**](docs/KURZANLEITUNG.md) — alles Wichtige nach Rollen
|
||||||
|
> sortiert, auf ein paar Seiten.
|
||||||
|
|
||||||
## Schnellstart
|
## Schnellstart
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -43,7 +49,8 @@ Vollständige Planung mit Aufwänden: [docs/ROADMAP.md](docs/ROADMAP.md) und
|
|||||||
|
|
||||||
| Datei | Inhalt |
|
| Datei | Inhalt |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [docs/ANLEITUNG.md](docs/ANLEITUNG.md) | Bedienung mit Bildschirmfotos |
|
| [docs/KURZANLEITUNG.md](docs/KURZANLEITUNG.md) | **Der Einstieg.** Nach Rollen sortiert, ein paar Seiten |
|
||||||
|
| [docs/ANLEITUNG.md](docs/ANLEITUNG.md) | Die vollständige Bedienung mit Bildschirmfotos, zum Nachschlagen |
|
||||||
| [docs/ROADMAP.md](docs/ROADMAP.md) | Phasenplan und was jeweils entsteht |
|
| [docs/ROADMAP.md](docs/ROADMAP.md) | Phasenplan und was jeweils entsteht |
|
||||||
| [docs/AUFWANDSSCHAETZUNG.md](docs/AUFWANDSSCHAETZUNG.md) | Aufwand je Phase, Schätzung gegen Ist |
|
| [docs/AUFWANDSSCHAETZUNG.md](docs/AUFWANDSSCHAETZUNG.md) | Aufwand je Phase, Schätzung gegen Ist |
|
||||||
| [docs/DEVLOG.md](docs/DEVLOG.md) | Entwicklungstagebuch mit Zeiterfassung |
|
| [docs/DEVLOG.md](docs/DEVLOG.md) | Entwicklungstagebuch mit Zeiterfassung |
|
||||||
@@ -1655,6 +1662,30 @@ ohne Traefik. In Produktion terminiert Traefik TLS und verteilt auf
|
|||||||
Der App-Container wendet beim Start ausstehende Migrationen an; der Worker
|
Der App-Container wendet beim Start ausstehende Migrationen an; der Worker
|
||||||
tut das bewusst nicht, damit nicht zwei Prozesse gleichzeitig migrieren.
|
tut das bewusst nicht, damit nicht zwei Prozesse gleichzeitig migrieren.
|
||||||
|
|
||||||
|
### Aktualisieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./update.sh --pruefen # nachsehen, ob es etwas Neues gibt
|
||||||
|
./update.sh # einspielen
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Ablauf ist so gebaut, dass er an jeder Stelle abbrechen darf, ohne eine
|
||||||
|
halb aktualisierte Anlage zu hinterlassen:
|
||||||
|
|
||||||
|
1. Nachsehen, ob überhaupt etwas Neues da ist — sonst endet es hier.
|
||||||
|
2. **Sichern.** Vor allem anderen; ein Update ist der häufigste Anlass, eine
|
||||||
|
Sicherung zu brauchen.
|
||||||
|
3. Neue Fassung holen und die Abbilder bauen. Schlägt das fehl, läuft die alte
|
||||||
|
Fassung unverändert weiter — es wurde noch nichts umgeschaltet.
|
||||||
|
4. Umschalten. Der App-Container spielt dabei die Migrationen ein.
|
||||||
|
5. Nachsehen, ob die Anwendung wieder antwortet. Tut sie es nach zweieinhalb
|
||||||
|
Minuten nicht, stehen die letzten Protokollzeilen da und die Befehle, mit
|
||||||
|
denen der vorherige Stand zurückkommt.
|
||||||
|
|
||||||
|
Vorher prüft das Skript, dass Docker, git und die `.env` da sind und dass im
|
||||||
|
Verzeichnis keine unverbuchten Änderungen liegen. `--ohne-sicherung` gibt es
|
||||||
|
für Testinstallationen; im Betrieb gehört es nicht benutzt.
|
||||||
|
|
||||||
## Datenschutz
|
## Datenschutz
|
||||||
|
|
||||||
- Keine Telemetrie, keine externen CDNs, keine Google Fonts. Es werden nur
|
- Keine Telemetrie, keine externen CDNs, keine Google Fonts. Es werden nur
|
||||||
|
|||||||
+6
-2
@@ -1,7 +1,11 @@
|
|||||||
# Anleitung
|
# Anleitung
|
||||||
|
|
||||||
Bedienung von FLORIAN, Stand Phase 11 (Version 0.12.0). Die Bilder zeigen die
|
Bedienung von FLORIAN, Version 1.0.0. Die Bilder zeigen die Beispielwehr aus
|
||||||
Beispielwehr aus dem Seed und werden mit `npm run screenshots` neu erzeugt.
|
dem Seed und werden mit `npm run screenshots` neu erzeugt.
|
||||||
|
|
||||||
|
> **Das hier ist zum Nachschlagen, nicht zum Durchlesen.** Wer anfangen will,
|
||||||
|
> nimmt die [Kurzanleitung](KURZANLEITUNG.md) — nach Rollen sortiert, auf ein
|
||||||
|
> paar Seiten.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+101
-1
@@ -24,7 +24,107 @@ Die aufgewendete Zeit wandert nach jedem Abschnitt in die Spalte
|
|||||||
| Phase 9 — Kassenverwaltung | 4:10 h | 1 | 04.09.2026 |
|
| Phase 9 — Kassenverwaltung | 4:10 h | 1 | 04.09.2026 |
|
||||||
| Phase 10 — Mandantenfähigkeit | 4:20 h | 1 | 05.09.2026 |
|
| Phase 10 — Mandantenfähigkeit | 4:20 h | 1 | 05.09.2026 |
|
||||||
| Phase 11 — Freigabereife | 2:40 h | 1 | 05.09.2026 |
|
| Phase 11 — Freigabereife | 2:40 h | 1 | 05.09.2026 |
|
||||||
| **Summe** | **35:30 h** | **14** | |
|
| 1.0.0 — Freigabe | 0:50 h | 1 | 06.09.2026 |
|
||||||
|
| **Summe** | **36:20 h** | **15** | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-09-06 · 1.0.0 — Freigabe
|
||||||
|
|
||||||
|
**09:20 – 10:10 · 0:50 h · Version 1.0.0**
|
||||||
|
|
||||||
|
### Was entstanden ist
|
||||||
|
|
||||||
|
Kein Modul. Zwei Dinge, die zum Betrieb gehören, und eine Entscheidung.
|
||||||
|
|
||||||
|
| Was | Wofür |
|
||||||
|
|---|---|
|
||||||
|
| `update.sh` | Aktualisieren auf einem Linux-Server, in einem Befehl |
|
||||||
|
| `docs/KURZANLEITUNG.md` | Der Einstieg für die Wehr, nach Rollen sortiert |
|
||||||
|
| Version 1.0.0 | Ab hier semantische Versionierung |
|
||||||
|
|
||||||
|
### Entscheidungen
|
||||||
|
|
||||||
|
**Das Update-Skript sichert, bevor es irgendetwas anfasst.** Ein Update ist
|
||||||
|
der häufigste Anlass, eine Sicherung zu brauchen — und der Moment, in dem
|
||||||
|
niemand daran denkt. `--ohne-sicherung` gibt es, aber mit einer Warnung und
|
||||||
|
nicht als Vorgabe.
|
||||||
|
|
||||||
|
**Die Reihenfolge ist so gewählt, dass ein Abbruch harmlos bleibt.** Holen und
|
||||||
|
Bauen passieren, bevor umgeschaltet wird: schlägt der Bau fehl, läuft die alte
|
||||||
|
Fassung unverändert weiter. Erst danach wird umgeschaltet, und ab da nennt die
|
||||||
|
Fehlerbehandlung die Befehle für den Rückweg.
|
||||||
|
|
||||||
|
**Die Kurzanleitung ist aufgabenorientiert, nicht modulorientiert.** Die große
|
||||||
|
Anleitung hat 63 Kapitel und ist nach Funktionsbereichen sortiert — richtig
|
||||||
|
zum Nachschlagen, unbrauchbar zum Anfangen. Die Kurzanleitung fragt stattdessen
|
||||||
|
„was willst du tun": ein Gerät prüfen, einen Kontoauszug einlesen, einen
|
||||||
|
Termin absagen.
|
||||||
|
|
||||||
|
**Sie erklärt bei jedem heiklen Punkt das Warum.** Warum ein abgeschlossenes
|
||||||
|
Prüfprotokoll nicht änderbar ist, warum gegengebucht statt korrigiert wird,
|
||||||
|
warum der Atemschutzhinweis stehen bleibt. Wer den Grund kennt, sucht nicht
|
||||||
|
nach dem Weg drumherum.
|
||||||
|
|
||||||
|
**Die Roadmap hakt nicht alles ab.** Zwei Freigabevoraussetzungen sind
|
||||||
|
organisatorisch und stehen aus — die Abnahme des Löschkonzepts und die
|
||||||
|
Schulung. Sie stehen mit ihrem Stand dort, statt stillschweigend als erledigt
|
||||||
|
zu gelten. Eine Freigabe, die abhakt, was nicht geprüft wurde, ist keine.
|
||||||
|
|
||||||
|
### Wo es gehakt hat
|
||||||
|
|
||||||
|
**Die Ursache der Testflackerei gefunden — nach elf Phasen.**
|
||||||
|
|
||||||
|
Beim abschließenden Durchlauf fielen wieder einmal fünf, dann sechs, dann ein
|
||||||
|
Test aus, in wechselnder Besetzung. Dieses Mal habe ich nicht weitergemacht,
|
||||||
|
sondern fünf Läufe hintereinander mitgeschrieben: betroffen waren immer
|
||||||
|
Zusicherungen über Trigger — „Wiki-Fassungen bleiben", „Mandantentrennung",
|
||||||
|
„abgeschlossene Prüfprotokolle".
|
||||||
|
|
||||||
|
`ki-datenbank.test.ts` und `atemschutz-datenbank.test.ts` schalteten beim
|
||||||
|
Aufräumen ihre Trigger mit `ALTER TABLE ... DISABLE TRIGGER USER` ab. Das ist
|
||||||
|
eine Schemaänderung: sie gilt für die ganze Datenbank und für alle. Vitest
|
||||||
|
fährt die Testdateien parallel — während also die eine Datei abräumte, prüfte
|
||||||
|
die andere gerade, dass genau dieser Trigger greift, und sah keinen.
|
||||||
|
|
||||||
|
`SET LOCAL session_replication_role = 'replica'` bewirkt dasselbe, aber nur in
|
||||||
|
der eigenen Transaktion. Danach: fünf Läufe hintereinander 601 grün.
|
||||||
|
|
||||||
|
Rückwirkend erklärt das eine Reihe von Ausfällen, die ich als Flackern
|
||||||
|
abgetan habe — sie waren keines. Die Lehre ist unbequem: „läuft einzeln durch,
|
||||||
|
also Umgebung" ist eine bequeme Erklärung und oft die falsche.
|
||||||
|
|
||||||
|
| Weiteres | Ursache | Lösung |
|
||||||
|
|---|---|---|
|
||||||
|
| Ein Datenbanktest ließ sich nur einmal ausführen | `mandanten-datenbank.test.ts` räumte nur am Ende auf; ein abgebrochener Lauf legte den nächsten lahm | Räumt jetzt auch am Anfang auf — dasselbe Muster wie in Phase 10 bei `ki-datenbank` |
|
||||||
|
|
||||||
|
### Abweichung von der Vorgabe
|
||||||
|
|
||||||
|
**`update.sh` ist nicht auf einem Linux-Server erprobt.** Entwickelt wurde
|
||||||
|
unter Windows; geprüft sind die Syntax (`bash -n`), die Hilfe, die
|
||||||
|
Argumentbehandlung und der Wächter gegen unverbuchte Änderungen — alles unter
|
||||||
|
Git Bash. Der eigentliche Ablauf (fetch, build, up, Gesundheitsprüfung) läuft
|
||||||
|
erst dort, wo FLORIAN wirklich steht.
|
||||||
|
|
||||||
|
Das ist keine Nachlässigkeit, sondern die Grenze dieser Umgebung. Beim ersten
|
||||||
|
echten Update gehört `./update.sh --pruefen` vorangestellt: es holt nichts und
|
||||||
|
ändert nichts, zeigt aber, ob Docker, git und die `.env` gefunden werden.
|
||||||
|
|
||||||
|
### Nachweise
|
||||||
|
|
||||||
|
- `npm run typecheck` — ohne Ausgabe
|
||||||
|
- `npm run lint` — ohne Befund
|
||||||
|
- `npm run test` — 601 Tests grün
|
||||||
|
- `bash -n update.sh` — Syntax in Ordnung
|
||||||
|
- `./update.sh --help` und `--quatsch` — Ausgabe und Ablehnung wie vorgesehen
|
||||||
|
- Der Wächter gegen unverbuchte Änderungen greift (an einem schmutzigen
|
||||||
|
Arbeitsverzeichnis geprüft)
|
||||||
|
- Alle acht Bilder der Kurzanleitung existieren
|
||||||
|
- `npm run test` **fünfmal hintereinander** — jedes Mal 601 grün. Das war
|
||||||
|
nach elf Phasen der erste Lauf, bei dem ich das behaupten kann
|
||||||
|
- `npm run test:e2e` — **268 grün, kein Fehlschlag, kein Flackern.** Ebenfalls
|
||||||
|
der erste vollständig saubere Durchlauf in diesem Projekt
|
||||||
|
- `npm run build` — sauber
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# FLORIAN — Kurzanleitung
|
||||||
|
|
||||||
|
Alles, was man am Anfang braucht, auf ein paar Seiten. Nach Rollen sortiert:
|
||||||
|
such dir den Abschnitt, der zu dir gehört, und lies den Rest später.
|
||||||
|
|
||||||
|
Ausführlich steht alles in der [Anleitung](ANLEITUNG.md) — 63 Kapitel, zum
|
||||||
|
Nachschlagen, nicht zum Durchlesen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Für alle
|
||||||
|
|
||||||
|
### Anmelden
|
||||||
|
|
||||||
|
FLORIAN läuft im Browser. Die Adresse bekommst du vom Kommandanten.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Nach der Anmeldung landest du auf der Übersicht: was ansteht, was fällig ist,
|
||||||
|
was gemeldet wurde.
|
||||||
|
|
||||||
|
**Das Passwort gehört nach der ersten Anmeldung geändert.** Oben rechts auf
|
||||||
|
deinen Namen.
|
||||||
|
|
||||||
|
### Was du überall findest
|
||||||
|
|
||||||
|
| Links in der Leiste | Wofür |
|
||||||
|
|---|---|
|
||||||
|
| **Übersicht** | Die Startseite: Fristen, Mängel, nächste Termine |
|
||||||
|
| **Termine** | Übungsabende, Lehrgänge, Versammlungen — mit Rückmeldung |
|
||||||
|
| **Dienstausweis** | Dein Ausweis mit QR-Code, auch fürs Handy |
|
||||||
|
| **Wissen** | Merkblätter und Dienstanweisungen der Wehr |
|
||||||
|
|
||||||
|
Du siehst nur, was zu deiner Rolle gehört. Fehlt dir etwas, sag dem
|
||||||
|
Kommandanten Bescheid — Rollen werden dort vergeben.
|
||||||
|
|
||||||
|
### Zu einem Termin zurückmelden
|
||||||
|
|
||||||
|
**Termine** öffnen, den Termin anklicken, **Zusagen** oder **Absagen**. Fertig.
|
||||||
|
Wer absagt, kann einen Grund angeben; das hilft dem Ausbilder bei der Planung.
|
||||||
|
|
||||||
|
### Am Terminal im Gerätehaus
|
||||||
|
|
||||||
|
Am Wandtablet meldest du dich mit **PIN oder Ausweiskarte** an — nicht mit
|
||||||
|
E-Mail und Passwort. Deine PIN bekommst du vom Kommandanten.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Dort geht: anmelden zum Dienst, einen Mangel melden, eine Fahrt eintragen,
|
||||||
|
Schutzkleidung zur Reinigung geben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Für den Gerätewart
|
||||||
|
|
||||||
|
Das ist der Bereich, in dem FLORIAN am meisten abnimmt.
|
||||||
|
|
||||||
|
### Der wichtigste Bildschirm: Prüfungen
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Unter **Prüfungen** steht, was wann fällig wird. Rot heißt überfällig, gelb
|
||||||
|
heißt demnächst. Die Fristen rechnet FLORIAN selbst aus den Prüfintervallen
|
||||||
|
der Gerätetypen aus — du musst nichts nachhalten.
|
||||||
|
|
||||||
|
### Ein Gerät prüfen
|
||||||
|
|
||||||
|
1. **Prüfungen** → das fällige Gerät anklicken → **Prüfung durchführen**.
|
||||||
|
2. Die Checkliste durchgehen. Sie kommt aus der Wartungsvorlage des Gerätetyps.
|
||||||
|
3. Ergebnis eintragen, unterschreiben, abschließen.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Danach entsteht ein **Prüfprotokoll als PDF** — das ist der Nachweis gegenüber
|
||||||
|
der Unfallkasse.
|
||||||
|
|
||||||
|
> **Achtung:** Ein abgeschlossenes Protokoll lässt sich nicht mehr ändern. Das
|
||||||
|
> ist Absicht — ein nachträglich änderbarer Nachweis wäre keiner. Ist etwas
|
||||||
|
> falsch, wird eine neue Prüfung erfasst.
|
||||||
|
|
||||||
|
### Ein Gerät anlegen
|
||||||
|
|
||||||
|
**Geräte** → **Gerät anlegen**. Pflicht sind nur Inventarnummer und
|
||||||
|
Bezeichnung; der Rest kann nachkommen.
|
||||||
|
|
||||||
|
Wähle möglichst einen **Gerätetyp** — daran hängen Prüfintervall und
|
||||||
|
Lebensdauer, und ohne Typ rechnet FLORIAN keine Frist.
|
||||||
|
|
||||||
|
### Einen Mangel bearbeiten
|
||||||
|
|
||||||
|
Mängel kommen meist vom Terminal — jemand meldet sie beim Einrücken.
|
||||||
|
|
||||||
|
**Mängel** → anklicken → jemandem zuweisen → nach der Behebung **erledigen**.
|
||||||
|
Ein Gerät mit einem schweren Mangel wird automatisch gesperrt.
|
||||||
|
|
||||||
|
### Was noch dazugehört
|
||||||
|
|
||||||
|
| Wo | Wofür |
|
||||||
|
|---|---|
|
||||||
|
| **Rückrufe** | Herstellerrückrufe: welche eurer Geräte betroffen sind |
|
||||||
|
| **Lebensdauer** | Helme, Schläuche, Schutzkleidung mit Höchstalter |
|
||||||
|
| **Fahrzeuge** | Beladung nach Geräteräumen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Für den Atemschutzgerätewart
|
||||||
|
|
||||||
|
> **Der wichtigste Satz zuerst:** Die Überwachungstafel am Fahrzeug bleibt
|
||||||
|
> führend. FLORIAN ergänzt sie, ersetzt sie nicht. Das steht auch dauerhaft
|
||||||
|
> über jeder Ansicht, und es soll dort stehen.
|
||||||
|
|
||||||
|
### Wer darf unter Pressluftatmer?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Atemschutz** zeigt jeden Träger mit seinem Stand: G26, Belastungsübung,
|
||||||
|
Einsatzübung. Wer eine der Voraussetzungen nicht erfüllt, steht rot da — mit
|
||||||
|
Klartext, woran es liegt, nicht nur mit einer Farbe.
|
||||||
|
|
||||||
|
### Eine G26 eintragen
|
||||||
|
|
||||||
|
**Atemschutz** → **Nachweise** → Person wählen → G26 erfassen. Untersuchungsdatum,
|
||||||
|
Ergebnis und Gültigkeit reichen; den Befund speichert FLORIAN bewusst nicht.
|
||||||
|
|
||||||
|
Dasselbe für Belastungsübungen.
|
||||||
|
|
||||||
|
### Eine Überwachung führen
|
||||||
|
|
||||||
|
**Atemschutz** → **Überwachung eröffnen**. Trupp zusammenstellen, Druckwerte
|
||||||
|
in Intervallen eintragen, Rückzugsdruck im Blick behalten. FLORIAN rechnet den
|
||||||
|
Rückzugsdruck und warnt, bevor es knapp wird.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Nach dem Ende entsteht ein vollständiges Protokoll als PDF.
|
||||||
|
|
||||||
|
> Solange ein Trupp im Einsatz ist, lässt sich die Überwachung nicht beenden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Für den Kommandanten
|
||||||
|
|
||||||
|
### Mitglieder und Rollen
|
||||||
|
|
||||||
|
**Mitglieder** → Person anklicken → Rollen vergeben.
|
||||||
|
|
||||||
|
Neun Rollen, jede mit festem Rechteumfang. Die wichtigsten: Gerätewart,
|
||||||
|
Atemschutzgerätewart, Kassier, Schriftführer, Ausbilder.
|
||||||
|
|
||||||
|
**Jemand kommt nicht hinein?** Dann hat die Person eine Akte, aber kein Konto.
|
||||||
|
Konten verwaltet, wer den Server betreibt — unter *Wehren und Konten*.
|
||||||
|
|
||||||
|
### Einen Einsatz erfassen
|
||||||
|
|
||||||
|
**Einsätze** → **Einsatz anlegen**. Die sechs Zeitstempel (Alarmierung,
|
||||||
|
Ausrücken, Eintreffen, Einsatzende, Einrücken, Einsatzbereitschaft), die
|
||||||
|
beteiligten Fahrzeuge und die Mannschaft.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Daraus entsteht der **Einsatzbericht als PDF** und die Statistik fürs Jahr.
|
||||||
|
|
||||||
|
### Kostenersatz abrechnen
|
||||||
|
|
||||||
|
Fehlalarm einer Brandmeldeanlage, Öleinsatz mit Verursacher, vermieteter
|
||||||
|
Schulungsraum: **Kasse** → **Rechnungen** → *Rechnung schreiben*. Die Nummer
|
||||||
|
vergibt FLORIAN fortlaufend.
|
||||||
|
|
||||||
|
### Wenn jemand Auskunft über seine Daten verlangt
|
||||||
|
|
||||||
|
Kommt selten vor, hat aber eine Frist von einem Monat.
|
||||||
|
|
||||||
|
**Mitglieder** → Person → **Datenauskunft** → *Auskunft erstellen*. Daraus
|
||||||
|
entsteht ein PDF mit allem, was über die Person gespeichert ist. Ausdrucken
|
||||||
|
und aushändigen, fertig.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Für den Kassier
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Buchen
|
||||||
|
|
||||||
|
**Kasse** → **Buchungen** → *Buchung erfassen*. Der Betrag ist immer positiv;
|
||||||
|
ob Einnahme oder Ausgabe, sagen die beiden Knöpfe daneben.
|
||||||
|
|
||||||
|
FLORIAN schlägt eine Kategorie vor — meist die richtige, weil er nachsieht,
|
||||||
|
wie ihr dieselbe Sache zuletzt gebucht habt.
|
||||||
|
|
||||||
|
### Kontoauszug einlesen
|
||||||
|
|
||||||
|
Statt fünfzig Umsätze abzutippen: im Online-Banking den **Umsatzexport**
|
||||||
|
herunterladen (CAMT.053 oder MT940), dann **Kasse** → **Buchungen** →
|
||||||
|
*Kontoauszug*.
|
||||||
|
|
||||||
|
Es entstehen **Entwürfe**, keine fertigen Buchungen — durchsehen musst du sie.
|
||||||
|
Umsätze, die schon in der Kasse stehen, erkennt FLORIAN und hakt sie nicht
|
||||||
|
noch einmal ab.
|
||||||
|
|
||||||
|
### Beiträge
|
||||||
|
|
||||||
|
Einmal im Jahr: **Kasse** → **Beiträge** → *Beitragslauf*. Vorher steht da, wie
|
||||||
|
viele Beiträge entstehen — und wer keinen passenden Satz hat.
|
||||||
|
|
||||||
|
Zahlungen hakst du in der Liste ab; dabei entsteht gleich die Buchung.
|
||||||
|
|
||||||
|
### Festschreiben — und was das bedeutet
|
||||||
|
|
||||||
|
Eine neue Buchung ist erst einmal ein **Entwurf** und änderbar. Mit
|
||||||
|
*Festschreiben* wird sie unveränderbar.
|
||||||
|
|
||||||
|
Ist danach etwas falsch, wird **gegengebucht**: eine zweite Buchung in die
|
||||||
|
andere Richtung, mit Begründung. Beide bleiben stehen. Das sieht nach mehr
|
||||||
|
Aufwand aus und ist genau der Punkt — eine verschwundene Buchung kann ein
|
||||||
|
Kassenprüfer nicht prüfen, eine berichtigte schon.
|
||||||
|
|
||||||
|
### Kassenprüfung
|
||||||
|
|
||||||
|
**Kasse** → **Abschluss** → *Kassenprüfung anlegen*. Links steht der Bestand
|
||||||
|
laut Buch, rechts trägst du ein, was gezählt wurde.
|
||||||
|
|
||||||
|
Bleibt eine Differenz, muss sie erklärt werden — sonst lässt sich der Abschluss
|
||||||
|
nicht festschreiben. In fünf Jahren weiß sonst niemand mehr, woher sie kam.
|
||||||
|
|
||||||
|
Danach entsteht der **Kassenprüfbericht als PDF** mit Unterschriftsfeldern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Für den, der den Server betreibt
|
||||||
|
|
||||||
|
### Sichern
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run sicherung
|
||||||
|
```
|
||||||
|
|
||||||
|
Sichert Datenbank **und** Dateien. Das Ergebnis gehört auf ein anderes Gerät —
|
||||||
|
eine Sicherung auf derselben Festplatte hilft gegen einen Festplattenfehler
|
||||||
|
nicht.
|
||||||
|
|
||||||
|
### Einmal im Jahr proben
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run wiederherstellen -- --quelle sicherungen/<Ordner>
|
||||||
|
```
|
||||||
|
|
||||||
|
So passiert nichts, es wird nur angezeigt, was geschähe. Erst mit `--wirklich`
|
||||||
|
wird zurückgespielt.
|
||||||
|
|
||||||
|
> Eine Sicherung, die nie zurückgespielt wurde, ist eine Vermutung. Der Ordner
|
||||||
|
> kann leer sein, der Auszug abgeschnitten — man erfährt es sonst an dem Tag,
|
||||||
|
> an dem man es nicht erfahren will.
|
||||||
|
|
||||||
|
### Aktualisieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./update.sh --pruefen # nachsehen, ob es etwas Neues gibt
|
||||||
|
./update.sh # einspielen
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Skript sichert von sich aus, bevor es etwas anfasst, und sagt bei einem
|
||||||
|
Fehler, wie der vorherige Stand zurückkommt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wenn etwas nicht geht
|
||||||
|
|
||||||
|
| Was | Woran es meist liegt |
|
||||||
|
|---|---|
|
||||||
|
| **Anmeldung geht nicht** | Nach fünf Fehlversuchen ist das Konto fünf Minuten gesperrt. Kurz warten. |
|
||||||
|
| **Ein Menüpunkt fehlt** | Die Rolle fehlt. Der Kommandant vergibt sie unter *Mitglieder*. |
|
||||||
|
| **Ein PDF entsteht nicht** | Es wird im Hintergrund erzeugt; nach ein paar Sekunden die Seite neu laden. |
|
||||||
|
| **Das Terminal meldet niemanden an** | PIN falsch, oder das Tablet ist nicht mehr eingerichtet. |
|
||||||
|
| **Eine Buchung lässt sich nicht ändern** | Sie ist festgeschrieben. Gegenbuchen. |
|
||||||
|
|
||||||
|
Steht die Antwort nicht dabei: [Anleitung, letztes Kapitel](ANLEITUNG.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*FLORIAN läuft auf einem Server, den die Feuerwehr selbst betreibt. Es gibt
|
||||||
|
keine Telemetrie, keine Cloud und keine Weitergabe an Dritte — die Daten
|
||||||
|
bleiben im Gerätehaus.*
|
||||||
+29
-9
@@ -21,7 +21,7 @@ Phase 8 ████████████████████ fertig
|
|||||||
Phase 9 ████████████████████ fertig 0.10.0
|
Phase 9 ████████████████████ fertig 0.10.0
|
||||||
Phase 10 ████████████████████ fertig 0.11.0
|
Phase 10 ████████████████████ fertig 0.11.0
|
||||||
Phase 11 ████████████████████ fertig 0.12.0
|
Phase 11 ████████████████████ fertig 0.12.0
|
||||||
Freigabe ░░░░░░░░░░░░░░░░░░░░ offen 1.0.0
|
Freigabe ████████████████████ fertig 1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -310,17 +310,37 @@ also genau die Probe, wegen der 1.0.0 sie verlangt.
|
|||||||
**Die Wehr kann jetzt:** eine Selbstauskunft erteilen, ihren Bestand sichern
|
**Die Wehr kann jetzt:** eine Selbstauskunft erteilen, ihren Bestand sichern
|
||||||
und ihn nachweislich zurückholen.
|
und ihn nachweislich zurückholen.
|
||||||
|
|
||||||
## 1.0.0 — Freigabe
|
## 1.0.0 — Freigabe · am 06.09.2026 erteilt
|
||||||
|
|
||||||
Kein neues Modul, sondern eine Entscheidung: die Wehr setzt FLORIAN im
|
Kein neues Modul, sondern eine Entscheidung: die Wehr setzt FLORIAN im
|
||||||
Echtbetrieb ein. Voraussetzungen:
|
Echtbetrieb ein.
|
||||||
|
|
||||||
- Datenübernahme aus der bisherigen Verwaltung abgeschlossen und abgeglichen
|
**Damit das nicht zur Formalie wird, hier der Stand jeder Voraussetzung — auch
|
||||||
- ~~Sicherungskonzept steht und ist einmal wiederhergestellt worden~~ — mit Phase 11 erledigt
|
der offenen.** Eine Freigabe, die alles abhakt, was ohnehin nicht geprüft
|
||||||
- Schulung von Kommandant, Gerätewart und Atemschutzgerätewart erfolgt
|
wurde, ist keine.
|
||||||
- Löschkonzept nach DSGVO abgenommen (die Auskunftsseite dazu gibt es seit Phase 11)
|
|
||||||
- Der Hinweis zur analogen Atemschutzüberwachung wird bewusst und
|
| Voraussetzung | Stand |
|
||||||
dokumentiert abgeschaltet — oder bewusst stehen gelassen
|
|---|---|
|
||||||
|
| Datenübernahme aus der bisherigen Verwaltung | **entfällt.** Die Wehr pflegt den Bestand von Hand ein; es gibt kein Altsystem, aus dem übernommen würde. Für Mitglieder, Fahrzeuge und Geräte steht der Import-Assistent bereit, falls sich das ändert. |
|
||||||
|
| Sicherungskonzept steht und ist einmal wiederhergestellt worden | **erledigt** mit Phase 11. Die Probe hat dabei einen Fehler gefunden, der sonst erst im Notfall aufgefallen wäre. |
|
||||||
|
| Löschkonzept nach DSGVO | **technisch vollständig** seit Phase 11: Auskunft nach Art. 15, Anonymisierung im Aufbewahrungslauf, konfigurierbare Fristen. Die Abnahme durch die Wehr ist ein organisatorischer Schritt und steht aus. |
|
||||||
|
| Schulung von Kommandant, Gerätewart und Atemschutzgerätewart | **steht aus.** Sie läuft nach der Freigabe; Grundlage ist die [Kurzanleitung](KURZANLEITUNG.md). |
|
||||||
|
| Hinweis zur analogen Atemschutzüberwachung | **bewusst stehen gelassen.** `ATEMSCHUTZ_HINWEIS=true`. FLORIAN bleibt damit ergänzend; die Tafel am Fahrzeug ist führend. Das Abschalten verlangt ohnehin einen Kommandantenbeschluss samt Fundstelle. |
|
||||||
|
|
||||||
|
Die beiden offenen Punkte sind **organisatorisch, nicht technisch**: an der
|
||||||
|
Software hängt nichts davon ab. Sie stehen hier, damit später nachlesbar ist,
|
||||||
|
was zum Zeitpunkt der Freigabe erledigt war und was nicht.
|
||||||
|
|
||||||
|
### Was 1.0.0 bedeutet — und was nicht
|
||||||
|
|
||||||
|
**Bedeutet:** Der Funktionsumfang ist vollständig, die Datenbank ist stabil,
|
||||||
|
Migrationen sind aufwärtskompatibel, und ab hier gilt semantische
|
||||||
|
Versionierung. Ein Update bricht nichts, ohne dass es in der Hauptnummer
|
||||||
|
steht.
|
||||||
|
|
||||||
|
**Bedeutet nicht:** dass keine Fehler mehr da sind. Es bedeutet, dass die
|
||||||
|
Wehr echte Daten hineingeben kann, ohne befürchten zu müssen, sie beim
|
||||||
|
nächsten Update zu verlieren.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "florian",
|
"name": "florian",
|
||||||
"version": "0.12.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "FLORIAN — Feuerwehr-Verwaltungsplattform",
|
"description": "FLORIAN — Feuerwehr-Verwaltungsplattform",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -77,11 +77,15 @@ afterAll(async () => {
|
|||||||
// Protokollzeilen lassen sich nicht loeschen — die Trigger sitzen auf
|
// Protokollzeilen lassen sich nicht loeschen — die Trigger sitzen auf
|
||||||
// den Tabellen. Fuer das Aufraeumen der Testdaten werden sie kurz
|
// den Tabellen. Fuer das Aufraeumen der Testdaten werden sie kurz
|
||||||
// abgeschaltet; im Betrieb passiert das nie.
|
// abgeschaltet; im Betrieb passiert das nie.
|
||||||
await tx.$executeRawUnsafe('ALTER TABLE "druckmessung" DISABLE TRIGGER USER');
|
// Trigger fuer das Aufraeumen stilllegen — sitzungslokal.
|
||||||
await tx.$executeRawUnsafe('ALTER TABLE "atemschutz_ereignis" DISABLE TRIGGER USER');
|
//
|
||||||
|
// NICHT ueber `ALTER TABLE ... DISABLE TRIGGER`: das ist eine
|
||||||
|
// Schemaänderung und gilt fuer alle. Vitest faehrt die Testdateien
|
||||||
|
// parallel, und waehrend hier abgeraeumt wurde, pruefte anderswo
|
||||||
|
// eine Datei, dass genau dieser Trigger greift — und sah keinen.
|
||||||
|
// `session_replication_role` wirkt nur in dieser Transaktion.
|
||||||
|
await tx.$executeRawUnsafe("SET LOCAL session_replication_role = 'replica'");
|
||||||
await tx.organisation.deleteMany({ where: { slug: SLUG } });
|
await tx.organisation.deleteMany({ where: { slug: SLUG } });
|
||||||
await tx.$executeRawUnsafe('ALTER TABLE "druckmessung" ENABLE TRIGGER USER');
|
|
||||||
await tx.$executeRawUnsafe('ALTER TABLE "atemschutz_ereignis" ENABLE TRIGGER USER');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await Promise.allSettled([app.$disconnect(), system.$disconnect()]);
|
await Promise.allSettled([app.$disconnect(), system.$disconnect()]);
|
||||||
|
|||||||
@@ -81,12 +81,15 @@ afterAll(async () => {
|
|||||||
if (erreichbar) {
|
if (erreichbar) {
|
||||||
await system.$transaction(async (tx) => {
|
await system.$transaction(async (tx) => {
|
||||||
await tx.$executeRaw`SELECT set_config('app.audit_bereinigung', 'an', true)`;
|
await tx.$executeRaw`SELECT set_config('app.audit_bereinigung', 'an', true)`;
|
||||||
// Der Unveränderlichkeits-Trigger sitzt auf ki_vorgang. Für das
|
// Trigger fuer das Aufraeumen stilllegen — sitzungslokal.
|
||||||
// Aufräumen der Testdaten wird er kurz abgeschaltet; im Betrieb
|
//
|
||||||
// passiert das nie.
|
// NICHT ueber `ALTER TABLE ... DISABLE TRIGGER`: das ist eine
|
||||||
await tx.$executeRawUnsafe('ALTER TABLE "ki_vorgang" DISABLE TRIGGER USER');
|
// Schemaänderung und gilt fuer alle. Vitest faehrt die Testdateien
|
||||||
|
// parallel, und waehrend hier abgeraeumt wurde, pruefte anderswo
|
||||||
|
// eine Datei, dass genau dieser Trigger greift — und sah keinen.
|
||||||
|
// `session_replication_role` wirkt nur in dieser Transaktion.
|
||||||
|
await tx.$executeRawUnsafe("SET LOCAL session_replication_role = 'replica'");
|
||||||
await tx.organisation.deleteMany({ where: { slug: SLUG } });
|
await tx.organisation.deleteMany({ where: { slug: SLUG } });
|
||||||
await tx.$executeRawUnsafe('ALTER TABLE "ki_vorgang" ENABLE TRIGGER USER');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await Promise.allSettled([app.$disconnect(), system.$disconnect()]);
|
await Promise.allSettled([app.$disconnect(), system.$disconnect()]);
|
||||||
|
|||||||
@@ -43,6 +43,15 @@ beforeAll(async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Erst aufraeumen, dann anlegen.
|
||||||
|
//
|
||||||
|
// Aufraeumen nur am Ende reicht nicht: bricht ein Lauf ab — abgebrochene
|
||||||
|
// Sitzung, Fehler in einer anderen Datei, geschlossenes Terminal —, bleiben
|
||||||
|
// die Datensaetze stehen und der naechste Lauf scheitert an einer
|
||||||
|
// Eindeutigkeit, die mit dem Geprueften nichts zu tun hat. Das kostet dann
|
||||||
|
// zehn Minuten Fehlersuche an der falschen Stelle.
|
||||||
|
await aufraeumen();
|
||||||
|
|
||||||
const a = await system.organisation.upsert({
|
const a = await system.organisation.upsert({
|
||||||
where: { slug: SLUG_A },
|
where: { slug: SLUG_A },
|
||||||
update: {},
|
update: {},
|
||||||
@@ -91,14 +100,17 @@ beforeAll(async () => {
|
|||||||
konto = benutzer.id;
|
konto = benutzer.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
/** Alles entfernen, was dieser Test anlegt. */
|
||||||
if (erreichbar) {
|
async function aufraeumen(): Promise<void> {
|
||||||
await system.$transaction(async (tx) => {
|
await system.$transaction(async (tx) => {
|
||||||
await tx.$executeRaw`SELECT set_config('app.audit_bereinigung', 'an', true)`;
|
await tx.$executeRaw`SELECT set_config('app.audit_bereinigung', 'an', true)`;
|
||||||
await tx.benutzer.deleteMany({ where: { email: { endsWith: "@test-mandant.de" } } });
|
await tx.benutzer.deleteMany({ where: { email: { endsWith: "@test-mandant.de" } } });
|
||||||
await tx.organisation.deleteMany({ where: { slug: { in: [SLUG_A, SLUG_B] } } });
|
await tx.organisation.deleteMany({ where: { slug: { in: [SLUG_A, SLUG_B] } } });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (erreichbar) await aufraeumen();
|
||||||
await system.$disconnect();
|
await system.$disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# FLORIAN aktualisieren.
|
||||||
|
#
|
||||||
|
# ./update.sh neue Fassung holen und einspielen
|
||||||
|
# ./update.sh --pruefen nur nachsehen, ob es etwas Neues gibt
|
||||||
|
# ./update.sh --ohne-sicherung für Testinstallationen
|
||||||
|
#
|
||||||
|
# Der Ablauf ist so gebaut, dass er an jeder Stelle abbrechen darf, ohne die
|
||||||
|
# Wehr mit einer halb aktualisierten Anlage zurückzulassen:
|
||||||
|
#
|
||||||
|
# 1. Nachsehen, ob überhaupt etwas Neues da ist.
|
||||||
|
# 2. SICHERN — Datenbank und Dateien. Vor allem anderen.
|
||||||
|
# 3. Neue Fassung holen und Abbilder bauen.
|
||||||
|
# 4. Umschalten. Der App-Container spielt dabei die Migrationen ein.
|
||||||
|
# 5. Nachsehen, ob die Anwendung wieder antwortet.
|
||||||
|
#
|
||||||
|
# Geht Schritt 3 schief, läuft die alte Fassung weiter — es wurde nichts
|
||||||
|
# angefasst. Geht Schritt 4 oder 5 schief, steht in der Ausgabe, mit welchem
|
||||||
|
# Befehl sich der vorherige Stand zurückholen lässt.
|
||||||
|
#
|
||||||
|
# Voraussetzungen: Linux, Docker mit Compose-Erweiterung, git, ein .env im
|
||||||
|
# selben Verzeichnis.
|
||||||
|
# =============================================================================
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
VERZEICHNIS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$VERZEICHNIS"
|
||||||
|
|
||||||
|
COMPOSE=(docker compose -f docker-compose.yml)
|
||||||
|
NUR_PRUEFEN=false
|
||||||
|
OHNE_SICHERUNG=false
|
||||||
|
|
||||||
|
for argument in "$@"; do
|
||||||
|
case "$argument" in
|
||||||
|
--pruefen) NUR_PRUEFEN=true ;;
|
||||||
|
--ohne-sicherung) OHNE_SICHERUNG=true ;;
|
||||||
|
-h|--help)
|
||||||
|
sed -n '3,20p' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unbekannte Angabe: $argument" >&2
|
||||||
|
echo "Bekannt sind: --pruefen, --ohne-sicherung, --help" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Ausgabe ----------------------------------------------------------------
|
||||||
|
# Farben nur, wenn wirklich ein Terminal dranhängt. In einem Cron-Protokoll
|
||||||
|
# sind Steuerzeichen nur Müll.
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
ROT=$'\033[31m'; GRUEN=$'\033[32m'; GELB=$'\033[33m'; FETT=$'\033[1m'; AUS=$'\033[0m'
|
||||||
|
else
|
||||||
|
ROT=''; GRUEN=''; GELB=''; FETT=''; AUS=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
schritt() { echo; echo "${FETT}==> $*${AUS}"; }
|
||||||
|
hinweis() { echo " $*"; }
|
||||||
|
warnung() { echo "${GELB} $*${AUS}"; }
|
||||||
|
fehler() { echo "${ROT}FEHLER: $*${AUS}" >&2; }
|
||||||
|
|
||||||
|
# Wo wir standen, bevor etwas verändert wurde — für die Rückkehr.
|
||||||
|
STAND_VORHER=""
|
||||||
|
SICHERUNG=""
|
||||||
|
|
||||||
|
abbruch() {
|
||||||
|
local zeile=$1
|
||||||
|
echo
|
||||||
|
fehler "Abbruch in Zeile $zeile."
|
||||||
|
echo
|
||||||
|
if [ -n "$STAND_VORHER" ]; then
|
||||||
|
echo "Der vorherige Stand war: $STAND_VORHER"
|
||||||
|
echo "Zurueck damit:"
|
||||||
|
echo " git checkout $STAND_VORHER"
|
||||||
|
echo " ${COMPOSE[*]} up -d --build"
|
||||||
|
fi
|
||||||
|
if [ -n "$SICHERUNG" ]; then
|
||||||
|
echo
|
||||||
|
echo "Die Sicherung von vorhin liegt in: $SICHERUNG"
|
||||||
|
echo "Zurueckspielen (ueberschreibt den Bestand):"
|
||||||
|
echo " npm run wiederherstellen -- --quelle $SICHERUNG --wirklich"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
trap 'abbruch $LINENO' ERR
|
||||||
|
|
||||||
|
# --- Voraussetzungen --------------------------------------------------------
|
||||||
|
schritt "Voraussetzungen"
|
||||||
|
|
||||||
|
for werkzeug in git docker; do
|
||||||
|
if ! command -v "$werkzeug" >/dev/null 2>&1; then
|
||||||
|
fehler "$werkzeug ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! docker compose version >/dev/null 2>&1; then
|
||||||
|
fehler "Docker Compose fehlt (die Erweiterung 'docker compose', nicht docker-compose)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
fehler "Es gibt keine .env in $VERZEICHNIS."
|
||||||
|
hinweis "Fuer eine Erstinstallation: cp .env.example .env und ausfuellen."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ein Update mit unverbuchten Aenderungen wuerde sie beim Holen ueberschreiben
|
||||||
|
# oder in einem Konflikt enden. Beides mitten in einem Update ist unschoen.
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
fehler "Im Verzeichnis liegen Aenderungen, die nicht eingecheckt sind."
|
||||||
|
git status --short | head -20
|
||||||
|
echo
|
||||||
|
hinweis "Bitte erst klaeren. Die .env gehoert nicht ins Git und stoert hier nicht."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
hinweis "Docker, git und .env sind da."
|
||||||
|
|
||||||
|
# --- Gibt es etwas Neues? ---------------------------------------------------
|
||||||
|
schritt "Nach einer neuen Fassung sehen"
|
||||||
|
|
||||||
|
ZWEIG="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
STAND_VORHER="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
git fetch --quiet origin "$ZWEIG"
|
||||||
|
|
||||||
|
STAND_NEU="$(git rev-parse "origin/$ZWEIG")"
|
||||||
|
|
||||||
|
if [ "$STAND_VORHER" = "$STAND_NEU" ]; then
|
||||||
|
hinweis "Alles aktuell — nichts zu tun."
|
||||||
|
hinweis "Stand: $(git log -1 --format='%h %s')"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ANZAHL="$(git rev-list --count "$STAND_VORHER..$STAND_NEU")"
|
||||||
|
hinweis "$ANZAHL neue Aenderung(en):"
|
||||||
|
git log --oneline --no-decorate "$STAND_VORHER..$STAND_NEU" | head -20 | sed 's/^/ /'
|
||||||
|
|
||||||
|
if [ "$NUR_PRUEFEN" = true ]; then
|
||||||
|
echo
|
||||||
|
hinweis "Nur nachgesehen — es wurde nichts geaendert."
|
||||||
|
hinweis "Zum Einspielen: ./update.sh"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Sichern ----------------------------------------------------------------
|
||||||
|
# Vor allem anderen, und ohne Umgehung im Regelfall. Ein Update ist der
|
||||||
|
# haeufigste Anlass, eine Sicherung zu brauchen.
|
||||||
|
if [ "$OHNE_SICHERUNG" = true ]; then
|
||||||
|
schritt "Sicherung uebersprungen"
|
||||||
|
warnung "--ohne-sicherung ist gesetzt. Bei einem Fehler gibt es kein Zurueck."
|
||||||
|
else
|
||||||
|
schritt "Sichern"
|
||||||
|
|
||||||
|
if ! command -v npm >/dev/null 2>&1; then
|
||||||
|
fehler "npm fehlt — ohne das laeuft das Sicherungsskript nicht."
|
||||||
|
hinweis "Entweder Node installieren oder bewusst mit --ohne-sicherung fahren."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
npm run --silent sicherung
|
||||||
|
SICHERUNG="$(find sicherungen -maxdepth 1 -mindepth 1 -type d | sort | tail -1)"
|
||||||
|
hinweis "Liegt in: $SICHERUNG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Holen und bauen --------------------------------------------------------
|
||||||
|
# Bis hierher ist nichts umgeschaltet: schlaegt der Bau fehl, laeuft die alte
|
||||||
|
# Fassung unveraendert weiter.
|
||||||
|
schritt "Neue Fassung holen"
|
||||||
|
git merge --ff-only "origin/$ZWEIG"
|
||||||
|
hinweis "Jetzt auf: $(git log -1 --format='%h %s')"
|
||||||
|
|
||||||
|
schritt "Abbilder bauen"
|
||||||
|
hinweis "Das dauert ein paar Minuten."
|
||||||
|
"${COMPOSE[@]}" build
|
||||||
|
|
||||||
|
# --- Umschalten -------------------------------------------------------------
|
||||||
|
# Der App-Container spielt beim Start die ausstehenden Migrationen ein; der
|
||||||
|
# Worker tut das bewusst nicht, damit nicht zwei Prozesse gleichzeitig
|
||||||
|
# migrieren.
|
||||||
|
schritt "Umschalten"
|
||||||
|
"${COMPOSE[@]}" up -d --remove-orphans
|
||||||
|
|
||||||
|
# --- Nachsehen --------------------------------------------------------------
|
||||||
|
schritt "Nachsehen, ob die Anwendung antwortet"
|
||||||
|
|
||||||
|
ADRESSE="http://localhost:3000/api/health"
|
||||||
|
if grep -q '^APP_URL=' .env; then
|
||||||
|
ADRESSE="$(grep '^APP_URL=' .env | head -1 | cut -d= -f2- | tr -d '"'"'"' ')/api/health"
|
||||||
|
fi
|
||||||
|
|
||||||
|
hinweis "Frage $ADRESSE ..."
|
||||||
|
|
||||||
|
GESUND=false
|
||||||
|
for versuch in $(seq 1 30); do
|
||||||
|
if curl -fsS --max-time 5 "$ADRESSE" >/dev/null 2>&1; then
|
||||||
|
GESUND=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
[ $((versuch % 6)) -eq 0 ] && hinweis " noch nicht da, warte weiter ($((versuch * 5)) s) ..."
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$GESUND" != true ]; then
|
||||||
|
fehler "Die Anwendung antwortet nach zweieinhalb Minuten nicht."
|
||||||
|
echo
|
||||||
|
echo "Die letzten Zeilen aus dem Protokoll:"
|
||||||
|
"${COMPOSE[@]}" logs --tail 40 app || true
|
||||||
|
echo
|
||||||
|
echo "Zurueck auf den vorherigen Stand:"
|
||||||
|
echo " git checkout $STAND_VORHER"
|
||||||
|
echo " ${COMPOSE[*]} up -d --build"
|
||||||
|
[ -n "$SICHERUNG" ] && echo " npm run wiederherstellen -- --quelle $SICHERUNG --wirklich"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ab hier ist nichts mehr rueckgaengig zu machen, was das Skript angerichtet
|
||||||
|
# haette — deshalb keine Fehlerfalle mehr, die zum Rueckbau raet.
|
||||||
|
trap - ERR
|
||||||
|
|
||||||
|
schritt "Fertig"
|
||||||
|
echo "${GRUEN} FLORIAN laeuft auf dem Stand $(git log -1 --format='%h %s')${AUS}"
|
||||||
|
echo
|
||||||
|
hinweis "Zustand der Dienste:"
|
||||||
|
"${COMPOSE[@]}" ps --format ' {{.Service}}\t{{.Status}}' 2>/dev/null || "${COMPOSE[@]}" ps
|
||||||
|
|
||||||
|
if [ -n "$SICHERUNG" ]; then
|
||||||
|
echo
|
||||||
|
hinweis "Die Sicherung von vorhin liegt in $SICHERUNG."
|
||||||
|
hinweis "Sie kann weg, sobald ein paar Tage nichts aufgefallen ist."
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user