Terminal im Geraetehaus mit Offline-Betrieb ergaenzen
Phase 4: eine eigene Oberflaeche unter /kiosk fuer das Wandtablet in der Fahrzeughalle — mit Handschuhen bedienbar und arbeitsfaehig, wenn das Netz weg ist. Datenmodell: Fahrt, Anwesenheit, Waescheabgabe, Inventurgang, Inventurposten, KioskVorgang und SyncKonflikt. Die Kennung eines Vorgangs erzeugt der Browser beim Einreihen und ist damit der Idempotenzschluessel; kommt derselbe Vorgang nach abgebrochener Uebertragung erneut an, erkennt ihn der Server wieder. Die Datenbank setzt vier Regeln selbst durch und verweigert UPDATE und DELETE auf kiosk_vorgang. Offline: von Hand geschriebener Service Worker, Warteschlange in der IndexedDB (Dexie), Fotos als Blob daneben, sichtbarer Zustand als Leiste ueber allem. Was sich beim Nachtragen nicht anwenden laesst, landet als Konflikt unter /terminal statt verworfen zu werden. Dazu: Anmeldung per Ausweis, PIN oder signierter QR-Marke vom Handy, Hallenmonitor mit optionalen DWD-Warnungen, Installation als App. Behoben: die Warteschlange zog ihren Wiederholungstakt bei jedem Rendern neu auf und haette einen liegen gebliebenen Vorgang nie ein zweites Mal geschickt. Gefunden vom Offline-E2E-Test. 31 neue Tests (20 Unit, 11 gegen eine echte Datenbank, 9 E2E je Browser). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d8d692098d
commit
791523027f
+120
-1
@@ -8,7 +8,126 @@ Die Regeln dazu stehen in [docs/VERSIONIERUNG.md](docs/VERSIONIERUNG.md).
|
||||
|
||||
## [Unveröffentlicht]
|
||||
|
||||
Nächster Schritt ist Phase 4 (Kiosk-PWA mit Offline-Betrieb).
|
||||
Nächster Schritt ist Phase 5 (Atemschutzverwaltung nach FwDV 7).
|
||||
|
||||
---
|
||||
|
||||
## [0.5.0] — 2026-09-03
|
||||
|
||||
Phase 4: das Terminal im Gerätehaus. Ein Wandtablet, das mit Handschuhen
|
||||
bedienbar ist und weiterarbeitet, wenn das Netz weg ist.
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
**Datenmodell**
|
||||
|
||||
- `Fahrt` — Fahrtenbuch je Fahrzeug: Fahrer, Zweck, Ziel, Kilometerstand bei
|
||||
Abfahrt und Rückkehr, getankte Menge. Eine Fahrt ohne Rückkehr ist die
|
||||
laufende Fahrt.
|
||||
- `Anwesenheit` — wer im Gerätehaus ist, mit Anlass (Dienst, Übung, Einsatz,
|
||||
Arbeitsdienst) und offener Abmeldung, solange jemand da ist.
|
||||
- `Waescheabgabe` — Schutzkleidung in der Reinigung, mit Grund und Stand von
|
||||
„abgegeben" bis „zurück".
|
||||
- `Inventurgang` und `Inventurposten` — der Gerätewart geht mit dem Tablet durch
|
||||
die Halle und scannt; am Ende steht die Liste dessen, was fehlt.
|
||||
- `KioskVorgang` — jeder vom Terminal übermittelte Vorgang, **mit der im
|
||||
Browser erzeugten Kennung als Primärschlüssel**. Das ist der Kern des
|
||||
Offline-Betriebs: kommt derselbe Vorgang nach einem abgebrochenen
|
||||
Sendeversuch ein zweites Mal an, wird er wiedererkannt statt neu angelegt.
|
||||
- `SyncKonflikt` — was sich beim Nachtragen nicht anwenden ließ. Nichts geht
|
||||
verloren, es wartet auf eine Entscheidung.
|
||||
|
||||
**Terminaloberfläche unter `/kiosk`**
|
||||
|
||||
- Kachelschirm: sieben Kacheln, jede mindestens 160 Pixel hoch, jede mit Symbol
|
||||
und Text, sichtbar nach Rechten. Keine Navigationsleiste.
|
||||
- Anmeldung auf drei Wegen: Ausweis auflegen (der RFID-Leser tippt die Kennung
|
||||
als Tastatureingabe, erkannt am Anschlagstempo), QR-Code vom Handy oder
|
||||
Mitgliedsnummer samt PIN.
|
||||
- Mangel melden in drei Schritten — Gerät scannen, Foto und ein Satz dazu,
|
||||
Dringlichkeit antippen. Anspruch war „unter dreißig Sekunden".
|
||||
- Kommen und Gehen mit zwei Tipps, Fahrtenbuch, Wäscheabgabe und Inventurgang.
|
||||
- Abmeldeuhr: nach `KIOSK_IDLE_TIMEOUT` Sekunden Untätigkeit meldet sich das
|
||||
Terminal von selbst ab, mit sichtbarer Warnung vorher.
|
||||
- Hallenmonitor unter `/kiosk/monitor`: Uhrzeit, laufender Einsatz, wer im Haus
|
||||
ist, Fahrzeuge, fällige Prüfungen und die nächsten Termine — Vollbild ohne
|
||||
Bedienung.
|
||||
|
||||
**Offline-Betrieb**
|
||||
|
||||
- Ein von Hand geschriebener Service Worker (`public/sw.js`): App-Shell und
|
||||
Stammdaten im Zwischenspeicher, `network-first` für Seiten mit
|
||||
Ausweichseite `/kiosk/offline`, `stale-while-revalidate` für lesende
|
||||
tRPC-Aufrufe. Schreibende Anfragen und alles unter `/api/auth` fasst er nicht
|
||||
an — eine zwischengespeicherte Anmeldung wäre eine Sicherheitslücke.
|
||||
- Warteschlange in einer IndexedDB (Dexie): **jede** schreibende Aktion geht
|
||||
zuerst dorthin, auch online. Sonst gäbe es zwei Wege durch den Code, und der
|
||||
seltenere wäre der ungetestete.
|
||||
- Fotos liegen als Blob in derselben Warteschlange und werden beim Übertragen
|
||||
zuerst hochgeladen.
|
||||
- Sichtbarer Zustand als Leiste über allem: verbunden, wie viele Änderungen
|
||||
warten, kein Netz — in Klartext, nicht nur als Farbe.
|
||||
- Erledigt heißt: angenommen **oder** als bereits verarbeitet erkannt **oder**
|
||||
in der Konfliktliste gelandet. In allen drei Fällen ist der Vorgang beim
|
||||
Server und darf aus der Warteschlange verschwinden.
|
||||
|
||||
**Anmeldemarke**
|
||||
|
||||
- `/anmeldemarke` zeigt einen QR-Code fürs Handy: signiert mit `AUTH_SECRET`,
|
||||
gültig 60 Sekunden, nicht gespeichert. Ein abfotografierter Code aus der
|
||||
Umkleide nützt nach einer Minute nichts mehr.
|
||||
|
||||
**Terminalübersicht in der Verwaltung**
|
||||
|
||||
- `/terminal` zeigt Konflikte zuerst — das sind die Vorgänge, die ein Mensch
|
||||
anfassen muss —, danach Anwesende, Wäsche und Fahrtenbuch.
|
||||
|
||||
**Installierbar als App**
|
||||
|
||||
- Web-App-Manifest, eigene Symbole (`npm run icons`), Startseite `/kiosk`. Kein
|
||||
externes CDN, keine fremde Schriftart.
|
||||
|
||||
**Wetterwarnungen (optional, aus)**
|
||||
|
||||
- Warnungen des DWD auf dem Hallenmonitor, hinter einer Schnittstelle in
|
||||
`lib/integrations/`. Standardmäßig **aus** — es ist die einzige Verbindung,
|
||||
die FLORIAN von sich aus nach außen aufbaut. Fällt sie aus, bleibt die Kachel
|
||||
leer.
|
||||
|
||||
**Sonstiges**
|
||||
|
||||
- Der Seed legt ein Fahrtenbuch mit vier Fahrten (eine läuft noch), vier
|
||||
Anwesenheiten (eine Person ist noch im Haus) und drei Wäscheabgaben an.
|
||||
- 31 neue Tests: 20 Unit-Tests zu Anmeldemarke und Warteschlange (letztere
|
||||
unter jsdom mit `fake-indexeddb`), 11 gegen eine echte Datenbank
|
||||
(Kilometerstände, Anwesenheitszeiten, Unveränderbarkeit der Vorgänge,
|
||||
Mandantentrennung) und 9 Playwright-Durchläufe je Browser — darunter der
|
||||
Offline-Fall mit `context.setOffline(true)` und der doppelt gesendete
|
||||
Vorgang, dessen Antwort unterwegs verloren geht.
|
||||
|
||||
### Geändert
|
||||
|
||||
- Die Datenbank setzt vier neue Regeln selbst durch: Kilometerstände laufen
|
||||
vorwärts, eine Rückkehr liegt nicht vor der Abfahrt, ein Gehen nicht vor dem
|
||||
Kommen, eine Wäscheabgabe umfasst mindestens ein Stück. Dazu `REVOKE UPDATE,
|
||||
DELETE` auf `kiosk_vorgang` — ein übermittelter Vorgang lässt sich nicht
|
||||
nachträglich glätten.
|
||||
- Die Beschriftung der DWD-Warnstufen liegt in
|
||||
`src/lib/integrations/wetter-texte.ts`, getrennt vom `server-only`-Modul
|
||||
daneben — dieselbe Trennung wie bei `qr.ts` und `qr-parser.ts`.
|
||||
- Neue Rechte `fahrtenbuch.lesen` und `fahrtenbuch.fuehren`, beide im
|
||||
Grundrechtebündel.
|
||||
|
||||
### Behoben
|
||||
|
||||
- Die Warteschlange rief sich selbst in eine Renderschleife: `jetztSenden`
|
||||
hatte das Mutationsobjekt von React Query in seinen Abhängigkeiten, und das
|
||||
ist bei jedem Rendern ein neues. Damit wurde der 20-Sekunden-Zeitgeber
|
||||
ständig neu aufgezogen und lief nie ab — ein liegen gebliebener Vorgang wäre
|
||||
ohne Zutun nie ein zweites Mal losgeschickt worden. Gefunden vom
|
||||
Offline-E2E-Test, der genau darauf wartet.
|
||||
- `/kiosk/einrichten` ließ sich nicht vorab erzeugen, weil `useSearchParams()`
|
||||
ohne Suspense-Grenze stand. Der Produktionsbuild brach dabei ab.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user