Version 1.0.0 — Freigabe für den Echtbetrieb #17
@@ -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