Files
FireAdministration/src/lib/nummernkreis.ts
T
Florian WildeandClaude Opus 5 91954c402b 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>
2026-09-03 13:50:58 +02:00

44 lines
1.6 KiB
TypeScript

/**
* Laufende Nummern je Wehr und Jahr.
*
* Prüfprotokolle, Einsätze und Übungen tragen dieselbe Art von Nummer:
* `JJJJ-NNNN`, fortlaufend innerhalb eines Jahres, je Wehr getrennt. Sie steht
* auf Papier und wird am Telefon genannt — deshalb kurz, sprechbar und ohne
* Bindestrichwüste.
*
* Die Nummern werden nie wiederverwendet. Vergeben wird die höchste vorhandene
* plus eins, nicht die Anzahl der Datensätze; sonst bekäme ein im
* Aufbewahrungslauf entfernter Vorgang seine Nummer an einen neuen vererbt.
*
* Reine Zeichenkettenarbeit, kein Datenbankzugriff: wer die Nummer vergibt,
* muss dabei selbst für eine Sperre sorgen (siehe `abschliessen` im
* Prüfungs-Router und `anlegen` im Einsatz-Router).
*/
/** Vier Stellen reichen für 9999 Vorgänge im Jahr — mehr hat keine Wehr. */
const STELLEN = 4;
/** Bildet "2026-0042". */
export function nummerBilden(jahr: number, laufend: number): string {
return `${jahr}-${String(laufend).padStart(STELLEN, "0")}`;
}
/** Zerlegt eine Nummer wieder. Gibt null zurück, wenn sie nicht passt. */
export function nummerLesen(
nummer: string | null | undefined,
): { jahr: number; laufend: number } | null {
if (!nummer) return null;
const treffer = /^(\d{4})-(\d{1,6})$/.exec(nummer.trim());
if (!treffer) return null;
return { jahr: Number(treffer[1]), laufend: Number(treffer[2]) };
}
/**
* Die nächste Nummer nach der bisher höchsten.
*
* @param hoechste die bisher höchste Nummer des Jahres, oder null
*/
export function naechsteNummer(jahr: number, hoechste: string | null | undefined): string {
return nummerBilden(jahr, (nummerLesen(hoechste)?.laufend ?? 0) + 1);
}