Phase 3: Einsatz- und Uebungserfassung

Schnellerfassung im Alarmfall, sechs Zeitstempel mit Ausrueckzeit, Anfahrt
und Hilfsfrist, Staerke nach FwDV 3, Personalvorschlag mit
Qualifikationsstand, Einsatzbericht als PDF, Jahresexport fuer die Gemeinde,
Statistik und Uebungen mit Anwesenheitsliste.

Dazu das Einsatzportal fuer Fremdwehren: ein signierter Link ohne Anmeldung,
zeitlich und mengenbegrenzt, der Stichwort, Ort, Zeiten und Lagemeldungen
zeigt und Meldungen mit Foto annimmt. Es sind die ersten oeffentlich
erreichbaren Endpunkte in FLORIAN.

Acht neue Tabellen, alle mit organisationId und Row Level Security. Ein
freigegebener Einsatzbericht ist unveraenderlich; ein Datenbanktrigger setzt
das durch. Koordinaten liegen doppelt — als Dezimalgrad und als
PostGIS-Punkt, zusammengehalten von einem Trigger.

57 neue Tests (32 Unit, 14 gegen eine echte Datenbank, 11 E2E).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-03 13:50:58 +02:00
co-authored by Claude Opus 5
parent bc62a9c1e8
commit 91954c402b
100 changed files with 9667 additions and 38 deletions
+27 -2
View File
@@ -13,6 +13,7 @@ import {
} from "@/lib/warteschlange";
import { ohneMandantenfilter, verbindungenTrennen } from "@/lib/db";
import { protokollErzeugen } from "@/lib/wartung/protokoll-erzeugen";
import { einsatzberichtErzeugen } from "@/lib/einsatz/bericht-erzeugen";
import { erinnerungenSenden, mangelBenachrichtigung, terminePlanen } from "@/worker/wartung";
/**
@@ -20,8 +21,8 @@ import { erinnerungenSenden, mangelBenachrichtigung, terminePlanen } from "@/wor
*
* Läuft als eigener Container neben der Anwendung und arbeitet die
* Warteschlange ab. Seit Phase 2 steckt die eigentliche Arbeit drin:
* Terminplanung, Erinnerungen und die Erzeugung der Prüfprotokolle. Die
* Aufbewahrung folgt in Phase 8.
* Terminplanung, Erinnerungen und die Erzeugung der Prüfprotokolle, ab Phase 3
* dazu die Einsatzberichte. Die Aufbewahrung folgt in Phase 8.
*
* Beim Start trägt sich der Prozess die wiederkehrenden Läufe je Wehr ein.
* Das ist bewusst hier und nicht in der Anwendung: der Worker ist der Einzige,
@@ -81,6 +82,29 @@ async function pdfErzeugen(auftrag: Job<AuftragsDaten>) {
return { erzeugt: ergebnis.neu, dateiId: ergebnis.dateiId };
}
async function einsatzberichtErzeugenAuftrag(auftrag: Job<AuftragsDaten>) {
const { organisationId, einsatzId } = auftrag.data as AuftragsDaten & { einsatzId?: string };
if (!einsatzId) {
throw new Error("Berichtsauftrag ohne Angabe, wozu — einsatzId fehlt.");
}
const ergebnis = await einsatzberichtErzeugen(organisationId, einsatzId);
if (!ergebnis) {
protokoll(`Kein Bericht für Einsatz ${einsatzId} — unbekannt oder nicht freigegeben.`);
return { erzeugt: false };
}
protokoll(
ergebnis.neu
? `Einsatzbericht für ${einsatzId} erzeugt.`
: `Einsatzbericht für ${einsatzId} lag bereits vor.`,
);
return { erzeugt: ergebnis.neu, dateiId: ergebnis.dateiId };
}
async function aufbewahrungPruefen(auftrag: Job<AuftragsDaten>): Promise<{ entfernt: number }> {
protokoll(`Aufbewahrungsfristen prüfen für Organisation ${auftrag.data.organisationId}`);
// Ab Phase 8: Audit-Log exportieren und danach abräumen,
@@ -92,6 +116,7 @@ const bearbeiter: Record<string, (auftrag: Job<AuftragsDaten>) => Promise<unknow
[AUFTRAG.FRISTEN_PRUEFEN]: fristenPruefen,
[AUFTRAG.ERINNERUNG_SENDEN]: erinnerungSenden,
[AUFTRAG.PDF_ERZEUGEN]: pdfErzeugen,
[AUFTRAG.EINSATZBERICHT_ERZEUGEN]: einsatzberichtErzeugenAuftrag,
[AUFTRAG.AUFBEWAHRUNG_PRUEFEN]: aufbewahrungPruefen,
};