Files
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

348 lines
14 KiB
SQL

-- =============================================================================
-- Phase 3: Tabellen für Einsätze und Übungen.
--
-- Erzeugt mit `prisma migrate dev --create-only` und wie in Phase 2 an einer
-- Stelle bereinigt: die DROP INDEX-Zeilen für die Volltext- und Geoindizes
-- sind entfernt. Prisma kennt gin_trgm_ops und gist nicht und würde sie bei
-- jeder Migration erneut löschen wollen.
--
-- Mandantentrennung, Koordinaten-Trigger, Unveränderbarkeit freigegebener
-- Berichte und die Suchindizes stehen in der nachfolgenden Migration.
-- =============================================================================
-- CreateEnum
CREATE TYPE "EinsatzKategorie" AS ENUM ('BRAND_KLEIN', 'BRAND_MITTEL', 'BRAND_GROSS', 'TECHNISCHE_HILFE_KLEIN', 'TECHNISCHE_HILFE_MITTEL', 'TECHNISCHE_HILFE_GROSS', 'GEFAHRGUT', 'RETTUNGSDIENST', 'BRANDMELDEANLAGE', 'BOESWILLIGER_ALARM', 'SICHERHEITSWACHE', 'UNTERSTUETZUNG', 'SONSTIGES');
-- CreateEnum
CREATE TYPE "EinsatzStatus" AS ENUM ('ENTWURF', 'LAUFEND', 'ABGESCHLOSSEN', 'FREIGEGEBEN');
-- CreateEnum
CREATE TYPE "Alarmierungsart" AS ENUM ('SIRENE', 'MELDEEMPFAENGER', 'TELEFON', 'APP', 'PERSOENLICH', 'SONSTIGES');
-- CreateEnum
CREATE TYPE "EinsatzFunktion" AS ENUM ('EINSATZLEITER', 'ZUGFUEHRER', 'GRUPPENFUEHRER', 'STAFFELFUEHRER', 'TRUPPFUEHRER', 'MASCHINIST', 'MELDER', 'TRUPPMANN', 'ATEMSCHUTZTRAEGER', 'SICHERHEITSTRUPP', 'SANITAETER', 'SONSTIGES');
-- CreateEnum
CREATE TYPE "UebungsArt" AS ENUM ('PRAXIS', 'THEORIE', 'ATEMSCHUTZ', 'MASCHINIST', 'FUNK', 'ERSTE_HILFE', 'GEMEINSCHAFTSUEBUNG', 'SONSTIGES');
-- CreateEnum
CREATE TYPE "LagemeldungQuelle" AS ENUM ('INTERN', 'PORTAL');
-- CreateTable
CREATE TABLE "einsatz" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"einsatznummer" VARCHAR(40) NOT NULL,
"stichwort" VARCHAR(160) NOT NULL,
"kategorie" "EinsatzKategorie" NOT NULL DEFAULT 'SONSTIGES',
"status" "EinsatzStatus" NOT NULL DEFAULT 'ENTWURF',
"ilsNummer" VARCHAR(60),
"alarmierungsart" "Alarmierungsart",
"strasse" VARCHAR(200),
"hausnummer" VARCHAR(20),
"plz" VARCHAR(10),
"ort" VARCHAR(120),
"ortsteil" VARCHAR(120),
"objekt" VARCHAR(200),
"breitengrad" DECIMAL(9,6),
"laengengrad" DECIMAL(9,6),
"koordinaten" geography(Point, 4326),
"koordinatenQuelle" VARCHAR(40),
"alarmiertUm" TIMESTAMPTZ(3),
"ausgeruecktUm" TIMESTAMPTZ(3),
"eingetroffenUm" TIMESTAMPTZ(3),
"einsatzendeUm" TIMESTAMPTZ(3),
"eingeruecktUm" TIMESTAMPTZ(3),
"einsatzbereitUm" TIMESTAMPTZ(3),
"einsatzleiterId" UUID,
"beschreibung" VARCHAR(4000),
"bericht" TEXT,
"wetter" VARCHAR(200),
"temperatur" INTEGER,
"weitereKraefte" VARCHAR(500),
"geretteteePersonen" INTEGER NOT NULL DEFAULT 0,
"verletztePersonen" INTEGER NOT NULL DEFAULT 0,
"totePersonen" INTEGER NOT NULL DEFAULT 0,
"sachschaden" DECIMAL(12,2),
"kostenpflichtig" BOOLEAN NOT NULL DEFAULT false,
"berichtDateiId" UUID,
"freigegebenAm" TIMESTAMPTZ(3),
"freigegebenVonId" UUID,
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geaendertAm" TIMESTAMPTZ(3) NOT NULL,
"geloeschtAm" TIMESTAMPTZ(3),
CONSTRAINT "einsatz_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "einsatzfahrzeug" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"einsatzId" UUID NOT NULL,
"fahrzeugId" UUID NOT NULL,
"ausgeruecktUm" TIMESTAMPTZ(3),
"eingetroffenUm" TIMESTAMPTZ(3),
"eingeruecktUm" TIMESTAMPTZ(3),
"kilometerAbfahrt" INTEGER,
"kilometerRueckkehr" INTEGER,
"wasserabgabe" INTEGER,
"bemerkung" VARCHAR(500),
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geaendertAm" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "einsatzfahrzeug_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "einsatzkraft" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"einsatzId" UUID NOT NULL,
"personId" UUID NOT NULL,
"funktion" "EinsatzFunktion" NOT NULL DEFAULT 'TRUPPMANN',
"fahrzeugId" UUID,
"vonUm" TIMESTAMPTZ(3),
"bisUm" TIMESTAMPTZ(3),
"atemschutz" BOOLEAN NOT NULL DEFAULT false,
"bemerkung" VARCHAR(500),
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geaendertAm" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "einsatzkraft_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "materialverbrauch" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"einsatzId" UUID NOT NULL,
"geraetId" UUID,
"bezeichnung" VARCHAR(200) NOT NULL,
"menge" DECIMAL(10,2) NOT NULL,
"einheit" VARCHAR(20) NOT NULL,
"kosten" DECIMAL(10,2),
"beschaedigt" BOOLEAN NOT NULL DEFAULT false,
"bemerkung" VARCHAR(500),
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "materialverbrauch_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "lagemeldung" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"einsatzId" UUID NOT NULL,
"zeitpunkt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"text" VARCHAR(2000) NOT NULL,
"quelle" "LagemeldungQuelle" NOT NULL DEFAULT 'INTERN',
"verfasserId" UUID,
"verfasserName" VARCHAR(200),
"fotoDateiId" UUID,
"wichtig" BOOLEAN NOT NULL DEFAULT false,
"portalZugangId" UUID,
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geloeschtAm" TIMESTAMPTZ(3),
CONSTRAINT "lagemeldung_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "portal_zugang" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"einsatzId" UUID NOT NULL,
"bezeichnung" VARCHAR(160) NOT NULL,
"tokenHash" VARCHAR(120) NOT NULL,
"gueltigBis" TIMESTAMPTZ(3) NOT NULL,
"widerrufenAm" TIMESTAMPTZ(3),
"aufrufe" INTEGER NOT NULL DEFAULT 0,
"letzterAufruf" TIMESTAMPTZ(3),
"letzteIp" INET,
"erstelltVonId" UUID,
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geaendertAm" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "portal_zugang_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "uebung" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"nummer" VARCHAR(40) NOT NULL,
"thema" VARCHAR(200) NOT NULL,
"art" "UebungsArt" NOT NULL DEFAULT 'PRAXIS',
"datum" DATE NOT NULL,
"beginnUm" TIMESTAMPTZ(3),
"endeUm" TIMESTAMPTZ(3),
"ort" VARCHAR(200),
"leiterId" UUID,
"lernziele" VARCHAR(2000),
"beschreibung" VARCHAR(4000),
"bericht" TEXT,
"berichtDateiId" UUID,
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geaendertAm" TIMESTAMPTZ(3) NOT NULL,
"geloeschtAm" TIMESTAMPTZ(3),
CONSTRAINT "uebung_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "uebungsteilnahme" (
"id" UUID NOT NULL,
"organisationId" UUID NOT NULL,
"uebungId" UUID NOT NULL,
"personId" UUID NOT NULL,
"funktion" "EinsatzFunktion",
"vonUm" TIMESTAMPTZ(3),
"bisUm" TIMESTAMPTZ(3),
"bemerkung" VARCHAR(500),
"erstelltAm" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "uebungsteilnahme_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "einsatz_organisationId_alarmiertUm_idx" ON "einsatz"("organisationId", "alarmiertUm" DESC);
-- CreateIndex
CREATE INDEX "einsatz_organisationId_status_idx" ON "einsatz"("organisationId", "status");
-- CreateIndex
CREATE INDEX "einsatz_organisationId_kategorie_idx" ON "einsatz"("organisationId", "kategorie");
-- CreateIndex
CREATE UNIQUE INDEX "einsatz_organisationId_einsatznummer_key" ON "einsatz"("organisationId", "einsatznummer");
-- CreateIndex
CREATE INDEX "einsatzfahrzeug_organisationId_einsatzId_idx" ON "einsatzfahrzeug"("organisationId", "einsatzId");
-- CreateIndex
CREATE INDEX "einsatzfahrzeug_organisationId_fahrzeugId_idx" ON "einsatzfahrzeug"("organisationId", "fahrzeugId");
-- CreateIndex
CREATE UNIQUE INDEX "einsatzfahrzeug_einsatzId_fahrzeugId_key" ON "einsatzfahrzeug"("einsatzId", "fahrzeugId");
-- CreateIndex
CREATE INDEX "einsatzkraft_organisationId_einsatzId_idx" ON "einsatzkraft"("organisationId", "einsatzId");
-- CreateIndex
CREATE INDEX "einsatzkraft_organisationId_personId_idx" ON "einsatzkraft"("organisationId", "personId");
-- CreateIndex
CREATE UNIQUE INDEX "einsatzkraft_einsatzId_personId_key" ON "einsatzkraft"("einsatzId", "personId");
-- CreateIndex
CREATE INDEX "materialverbrauch_organisationId_einsatzId_idx" ON "materialverbrauch"("organisationId", "einsatzId");
-- CreateIndex
CREATE INDEX "lagemeldung_organisationId_einsatzId_zeitpunkt_idx" ON "lagemeldung"("organisationId", "einsatzId", "zeitpunkt");
-- CreateIndex
CREATE INDEX "portal_zugang_organisationId_einsatzId_idx" ON "portal_zugang"("organisationId", "einsatzId");
-- CreateIndex
CREATE INDEX "portal_zugang_gueltigBis_idx" ON "portal_zugang"("gueltigBis");
-- CreateIndex
CREATE INDEX "uebung_organisationId_datum_idx" ON "uebung"("organisationId", "datum" DESC);
-- CreateIndex
CREATE INDEX "uebung_organisationId_art_idx" ON "uebung"("organisationId", "art");
-- CreateIndex
CREATE UNIQUE INDEX "uebung_organisationId_nummer_key" ON "uebung"("organisationId", "nummer");
-- CreateIndex
CREATE INDEX "uebungsteilnahme_organisationId_personId_idx" ON "uebungsteilnahme"("organisationId", "personId");
-- CreateIndex
CREATE UNIQUE INDEX "uebungsteilnahme_uebungId_personId_key" ON "uebungsteilnahme"("uebungId", "personId");
-- AddForeignKey
ALTER TABLE "einsatz" ADD CONSTRAINT "einsatz_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatz" ADD CONSTRAINT "einsatz_einsatzleiterId_fkey" FOREIGN KEY ("einsatzleiterId") REFERENCES "person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatz" ADD CONSTRAINT "einsatz_freigegebenVonId_fkey" FOREIGN KEY ("freigegebenVonId") REFERENCES "person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatz" ADD CONSTRAINT "einsatz_berichtDateiId_fkey" FOREIGN KEY ("berichtDateiId") REFERENCES "datei"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzfahrzeug" ADD CONSTRAINT "einsatzfahrzeug_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzfahrzeug" ADD CONSTRAINT "einsatzfahrzeug_einsatzId_fkey" FOREIGN KEY ("einsatzId") REFERENCES "einsatz"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzfahrzeug" ADD CONSTRAINT "einsatzfahrzeug_fahrzeugId_fkey" FOREIGN KEY ("fahrzeugId") REFERENCES "fahrzeug"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzkraft" ADD CONSTRAINT "einsatzkraft_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzkraft" ADD CONSTRAINT "einsatzkraft_einsatzId_fkey" FOREIGN KEY ("einsatzId") REFERENCES "einsatz"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzkraft" ADD CONSTRAINT "einsatzkraft_personId_fkey" FOREIGN KEY ("personId") REFERENCES "person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "einsatzkraft" ADD CONSTRAINT "einsatzkraft_fahrzeugId_fkey" FOREIGN KEY ("fahrzeugId") REFERENCES "fahrzeug"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "materialverbrauch" ADD CONSTRAINT "materialverbrauch_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "materialverbrauch" ADD CONSTRAINT "materialverbrauch_einsatzId_fkey" FOREIGN KEY ("einsatzId") REFERENCES "einsatz"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "materialverbrauch" ADD CONSTRAINT "materialverbrauch_geraetId_fkey" FOREIGN KEY ("geraetId") REFERENCES "geraet"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lagemeldung" ADD CONSTRAINT "lagemeldung_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lagemeldung" ADD CONSTRAINT "lagemeldung_einsatzId_fkey" FOREIGN KEY ("einsatzId") REFERENCES "einsatz"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lagemeldung" ADD CONSTRAINT "lagemeldung_verfasserId_fkey" FOREIGN KEY ("verfasserId") REFERENCES "person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lagemeldung" ADD CONSTRAINT "lagemeldung_fotoDateiId_fkey" FOREIGN KEY ("fotoDateiId") REFERENCES "datei"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lagemeldung" ADD CONSTRAINT "lagemeldung_portalZugangId_fkey" FOREIGN KEY ("portalZugangId") REFERENCES "portal_zugang"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "portal_zugang" ADD CONSTRAINT "portal_zugang_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "portal_zugang" ADD CONSTRAINT "portal_zugang_einsatzId_fkey" FOREIGN KEY ("einsatzId") REFERENCES "einsatz"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "portal_zugang" ADD CONSTRAINT "portal_zugang_erstelltVonId_fkey" FOREIGN KEY ("erstelltVonId") REFERENCES "person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "uebung" ADD CONSTRAINT "uebung_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "uebung" ADD CONSTRAINT "uebung_leiterId_fkey" FOREIGN KEY ("leiterId") REFERENCES "person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "uebung" ADD CONSTRAINT "uebung_berichtDateiId_fkey" FOREIGN KEY ("berichtDateiId") REFERENCES "datei"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "uebungsteilnahme" ADD CONSTRAINT "uebungsteilnahme_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "uebungsteilnahme" ADD CONSTRAINT "uebungsteilnahme_uebungId_fkey" FOREIGN KEY ("uebungId") REFERENCES "uebung"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "uebungsteilnahme" ADD CONSTRAINT "uebungsteilnahme_personId_fkey" FOREIGN KEY ("personId") REFERENCES "person"("id") ON DELETE CASCADE ON UPDATE CASCADE;