Workstream 7: Wehr-Bereich — Fuhrpark & Benutzer (Phase 4)

Implementiert den auf die eigene brigadeId beschränkten Wehr-Bereich:
Profil (inkl. Inline-Geocoding via geocodeAddress), Fuhrpark (Fahrzeug per
Vorlage oder frei, typisierter Merkmal-Editor), Geräte (Kategorie, Werte,
Zuordnung Fahrzeug/„im Gerätehaus") und Benutzerkonten (wehr_admin/wehr_read).

- Schema importiert (nicht neu definiert); ASCII-Property wehrfuehrer.
- Default-deny dreifach: Layout-Guard requireWehrAdmin() + jede Server Action
  beginnt mit requireWehrAdmin(); fremde Entities -> notFound() (404).
- Validierung an der Grenze (Zod): buildMerkmalValuesSchema validiert Werte
  typgerecht gegen die serverseitig aufgelösten Definitionen; Rolle auf
  wehr_admin|wehr_read beschränkt (platform_admin abgelehnt).
- upsertMerkmalValues delete-then-insert mit typisierter Drizzle-Tx (kein any);
  boolean false/num 0 gelten als gesetzt.
- argon2id-Einmalpasswort beim Benutzeranlegen; Selbst-Deaktivierung verhindert.
- Audit vollständig: brigade.profile_update, vehicle.create/update/delete/status,
  equipment.create/update/delete/status, user.create/deactivate.
- Vorgabewerte aus drei typisierten Spalten (vorgabewert_num/_text/_bool).
- i18n via zentraler de.ts; loading/empty/error-konforme Listen.

Tests: 22 neue Unit-Tests (vehicle/equipment/brigade-user-Validierung,
upsertMerkmalValues) grün; Playwright-Specs verwaltung-fuhrpark + -scoping
geschrieben (deferred: kein Server/DB in der Sandbox).

Verifikation offline: tsc --noEmit clean, eslint clean, vitest 147 passed,
next build exit 0 (alle /verwaltung/*-Routen), drizzle-kit check ohne Drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-06-09 11:06:17 +02:00
parent 628d35bfcd
commit 5cda09c411
39 changed files with 3201 additions and 0 deletions

View File

@@ -125,6 +125,63 @@ export const de = {
zurueck: "Zurück",
weiter: "Weiter",
},
verwaltung: {
titel: "Verwaltung",
navProfil: "Profil",
navFahrzeuge: "Fahrzeuge",
navGeraete: "Geräte",
navBenutzer: "Benutzer",
speichern: "Speichern",
abbrechen: "Abbrechen",
loeschen: "Löschen",
anlegen: "Anlegen",
bearbeiten: "Bearbeiten",
neu: "Neu",
name: "Name",
funkrufname: "Funkrufname",
notiz: "Notiz",
status: "Status",
kategorie: "Kategorie",
zuordnung: "Zuordnung",
imGeraetehaus: "im Gerätehaus",
vorlage: "Fahrzeug-Vorlage",
keineVorlage: "Ohne Vorlage (frei)",
vorlageWaehlen: "Vorlage wählen",
merkmale: "Merkmale",
keineMerkmale: "Für diese Auswahl sind keine Merkmale hinterlegt.",
fahrzeugAnlegen: "Fahrzeug anlegen",
fahrzeugBearbeiten: "Fahrzeug bearbeiten",
geraetAnlegen: "Gerät anlegen",
geraetBearbeiten: "Gerät bearbeiten",
keineFahrzeuge: "Noch keine Fahrzeuge erfasst.",
keineGeraete: "Noch keine Geräte erfasst.",
keineBenutzer: "Noch keine Benutzer erfasst.",
profilTitel: "Wehr-Profil",
profilGespeichert: "Profil gespeichert.",
geocodeOk: "Adresse geokodiert.",
geocodeWarnung:
"Adresse konnte nicht geokodiert werden. Daten wurden dennoch gespeichert.",
strasse: "Straße",
plz: "PLZ",
ort: "Ort",
email: "E-Mail",
telefon: "Telefon",
wehrfuehrer: "Wehrführer",
funkrufnameSchema: "Funkrufname-Schema",
rolle: "Rolle",
rolleAdmin: "Wehr-Admin",
rolleRead: "Lesend",
benutzerAnlegen: "Benutzer anlegen",
deaktivieren: "Deaktivieren",
aktiv: "aktiv",
inaktiv: "inaktiv",
authLokal: "lokal",
authAuthentik: "Authentik",
tempPasswort:
"Einmal-Passwort (nur jetzt sichtbar, bitte sicher übergeben):",
loeschenBestaetigen: "Wirklich löschen?",
pflichtfeld: "Pflichtfeld",
},
} as const;
type Leaf = string;