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:
co-authored by
Claude Opus 5
parent
bc62a9c1e8
commit
91954c402b
@@ -118,6 +118,26 @@ const AUFNAHMEN: Aufnahme[] = [
|
||||
beschreibung: "Abgeschlossene Prüfprotokolle",
|
||||
angemeldet: true,
|
||||
},
|
||||
|
||||
// Phase 3 — Einsätze und Übungen
|
||||
{
|
||||
name: "19-einsaetze",
|
||||
pfad: "/einsaetze",
|
||||
beschreibung: "Einsatzliste mit Schnellerfassung",
|
||||
angemeldet: true,
|
||||
},
|
||||
{
|
||||
name: "20-statistik",
|
||||
pfad: "/einsaetze/statistik",
|
||||
beschreibung: "Einsatzstatistik mit Hilfsfristen und Beteiligung",
|
||||
angemeldet: true,
|
||||
},
|
||||
{
|
||||
name: "21-uebungen",
|
||||
pfad: "/uebungen",
|
||||
beschreibung: "Übungen mit Thema und Teilnehmerzahl",
|
||||
angemeldet: true,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -163,6 +183,18 @@ const DYNAMISCHE_AUFNAHMEN: Array<{
|
||||
// Nur fertige Protokolle: die tragen eine Nummer, Entwürfe nicht.
|
||||
textMuster: /^\d{4}-\d{4}$/,
|
||||
},
|
||||
{
|
||||
name: "22-einsatz",
|
||||
beschreibung: "Einsatzdetail mit Zeitleiste, Stärke und Verlauf",
|
||||
von: "/einsaetze",
|
||||
linkMuster: /^\/einsaetze\/[0-9a-f-]{36}$/,
|
||||
},
|
||||
{
|
||||
name: "23-uebung",
|
||||
beschreibung: "Übung mit Anwesenheitsliste",
|
||||
von: "/uebungen",
|
||||
linkMuster: /^\/uebungen\/[0-9a-f-]{36}$/,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -201,6 +233,49 @@ async function anmelden(seite: Page): Promise<void> {
|
||||
await seite.waitForLoadState("networkidle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Das Einsatzportal lässt sich nicht über eine feste Adresse aufrufen — der
|
||||
* Link entsteht erst, wenn ihn jemand erzeugt. Also erzeugt ihn dieses Skript
|
||||
* selbst, im ersten Einsatz der Liste, und fotografiert das Ergebnis.
|
||||
*/
|
||||
async function portalAufnehmen(seite: Page, darstellung: string): Promise<void> {
|
||||
try {
|
||||
await seite.goto(`${BASIS}/einsaetze`, { waitUntil: "networkidle" });
|
||||
await fertigGeladen(seite);
|
||||
|
||||
const ziele = await seite
|
||||
.locator("a[href]")
|
||||
.evaluateAll((elemente) =>
|
||||
elemente.map((element) => element.getAttribute("href") ?? "").filter(Boolean),
|
||||
);
|
||||
|
||||
const einsatz = ziele.find((href) => /^\/einsaetze\/[0-9a-f-]{36}$/.test(href));
|
||||
if (!einsatz) {
|
||||
console.warn("[screenshots] Kein Einsatz für das Portal gefunden — übersprungen.");
|
||||
return;
|
||||
}
|
||||
|
||||
await seite.goto(`${BASIS}${einsatz}`, { waitUntil: "networkidle" });
|
||||
await fertigGeladen(seite);
|
||||
|
||||
await seite.getByLabel("Für wen ist der Link?").fill("FF Nachbarhausen");
|
||||
await seite.getByRole("button", { name: "Erzeugen" }).click();
|
||||
|
||||
const linkfeld = seite.locator("input[readonly]").first();
|
||||
await linkfeld.waitFor({ timeout: 15_000 });
|
||||
const adresse = await linkfeld.inputValue();
|
||||
|
||||
await seite.goto(adresse, { waitUntil: "networkidle" });
|
||||
await fertigGeladen(seite);
|
||||
|
||||
const datei = path.join(ZIEL, `24-portal-${darstellung}.png`);
|
||||
await seite.screenshot({ path: datei, fullPage: true });
|
||||
console.info(`[screenshots] Einsatzportal für Fremdwehren (${darstellung}) -> ${datei}`);
|
||||
} catch (fehler) {
|
||||
console.warn("[screenshots] Portalaufnahme fehlgeschlagen:", fehler);
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
await mkdir(ZIEL, { recursive: true });
|
||||
|
||||
@@ -266,6 +341,8 @@ async function main(): Promise<void> {
|
||||
console.info(`[screenshots] ${aufnahme.beschreibung} (${darstellung}) -> ${datei}`);
|
||||
}
|
||||
|
||||
await portalAufnehmen(seite, darstellung);
|
||||
|
||||
await kontext.close();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user