feat(datenmodell): Schema, tagCodes und Geheimnisse (#20)
Vollständiges Datenmodell in deutscher Benennung: 20 Tabellen, 9 Aufzählungen, eine Migration, dazu die framework-freien Helfer für tagCodes und Geheimnisse. Regeln stehen zusätzlich in der Datenbank, nicht nur im Service: 12 CHECKs, 2 partielle Eindeutigkeitsindizes, 4 zusammengesetzte Fremdschlüssel. Die Mandantengrenze ist damit strukturell erzwungen. Schrittergebnisse, Kommentare und Protokolleinträge kaskadieren bewusst nicht — das Archiv ist das Gedächtnis der Anwendung. 106 Tests, davon 42 gegen die echten Datenbankregeln. Closes #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #20.
This commit is contained in:
@@ -0,0 +1,734 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Rolle" AS ENUM ('ADMIN', 'REDAKTEUR', 'TECHNIKER', 'LESER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Betriebsmodus" AS ENUM ('INTERN', 'DIENSTLEISTUNG');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LaufStatus" AS ENUM ('OFFEN', 'IN_ARBEIT', 'ABGESCHLOSSEN', 'ARCHIVIERT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SchrittZustand" AS ENUM ('OFFEN', 'ERLEDIGT', 'NICHT_ZUTREFFEND', 'FEHLGESCHLAGEN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SchrittTyp" AS ENUM ('HAKEN', 'TEXT', 'ZAHL', 'AUSWAHL', 'DATUM', 'BEFEHL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VariablenTyp" AS ENUM ('TEXT', 'ZAHL', 'AUSWAHL', 'DATUM');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "GeraeteTyp" AS ENUM ('PC', 'NOTEBOOK', 'SERVER', 'FIREWALL', 'SWITCH', 'DRUCKER', 'RAUM', 'SONSTIGES');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VersionStatus" AS ENUM ('ENTWURF', 'VEROEFFENTLICHT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Herkunft" AS ENUM ('ABGLEICH', 'MANUELL');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Einstellung" (
|
||||
"id" TEXT NOT NULL DEFAULT 'global',
|
||||
"modus" "Betriebsmodus" NOT NULL DEFAULT 'INTERN',
|
||||
"namensregeln" JSONB NOT NULL DEFAULT '{}',
|
||||
"gesperrteOu" TEXT,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Einstellung_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Kunde" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"kuerzel" TEXT NOT NULL,
|
||||
"istEigeneOrganisation" BOOLEAN NOT NULL DEFAULT false,
|
||||
"namensregeln" JSONB NOT NULL DEFAULT '{}',
|
||||
"notizen" TEXT,
|
||||
"archiviert" BOOLEAN NOT NULL DEFAULT false,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Kunde_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Benutzer" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"rolle" "Rolle" NOT NULL DEFAULT 'LESER',
|
||||
"oidcSub" TEXT,
|
||||
"passwortHash" TEXT,
|
||||
"aktiv" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sitzungenUngueltigAb" TIMESTAMP(3),
|
||||
"letzteAnmeldungAm" TIMESTAMP(3),
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Benutzer_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BenutzerKunde" (
|
||||
"benutzerId" TEXT NOT NULL,
|
||||
"kundeId" TEXT NOT NULL,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "BenutzerKunde_pkey" PRIMARY KEY ("benutzerId","kundeId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Verzeichnisquelle" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kundeId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"aktiv" BOOLEAN NOT NULL DEFAULT false,
|
||||
"url" TEXT NOT NULL,
|
||||
"bindDn" TEXT NOT NULL,
|
||||
"bindPasswortVerschluesselt" TEXT NOT NULL,
|
||||
"basisDn" TEXT NOT NULL,
|
||||
"filter" TEXT NOT NULL,
|
||||
"attributZuordnung" JSONB NOT NULL,
|
||||
"caZertifikatPem" TEXT,
|
||||
"abgleichIntervallMinuten" INTEGER NOT NULL DEFAULT 60,
|
||||
"letzterAbgleichAm" TIMESTAMP(3),
|
||||
"letzterAbgleichStatus" TEXT,
|
||||
"letzterAbgleichFehler" TEXT,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Verzeichnisquelle_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Mitarbeiter" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kundeId" TEXT NOT NULL,
|
||||
"quelleId" TEXT,
|
||||
"externeId" TEXT,
|
||||
"vorname" TEXT NOT NULL,
|
||||
"nachname" TEXT NOT NULL,
|
||||
"anzeigename" TEXT NOT NULL,
|
||||
"mail" TEXT,
|
||||
"upn" TEXT,
|
||||
"sam" TEXT,
|
||||
"dn" TEXT,
|
||||
"abteilung" TEXT,
|
||||
"titel" TEXT,
|
||||
"vorgesetzterExterneId" TEXT,
|
||||
"aktiv" BOOLEAN NOT NULL DEFAULT true,
|
||||
"herkunft" "Herkunft" NOT NULL DEFAULT 'MANUELL',
|
||||
"zuletztGesehenAm" TIMESTAMP(3),
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Mitarbeiter_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Abteilung" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kundeId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"zielOu" TEXT,
|
||||
"standardGruppen" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"mailDomain" TEXT,
|
||||
"notizen" TEXT,
|
||||
"archiviert" BOOLEAN NOT NULL DEFAULT false,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Abteilung_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Geraet" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kundeId" TEXT NOT NULL,
|
||||
"mitarbeiterId" TEXT,
|
||||
"tagCode" TEXT NOT NULL,
|
||||
"hostname" TEXT,
|
||||
"bezeichnung" TEXT,
|
||||
"seriennummer" TEXT,
|
||||
"inventarnummer" TEXT,
|
||||
"typ" "GeraeteTyp" NOT NULL DEFAULT 'SONSTIGES',
|
||||
"modell" TEXT,
|
||||
"notizen" TEXT,
|
||||
"archiviert" BOOLEAN NOT NULL DEFAULT false,
|
||||
"registriertAm" TIMESTAMP(3),
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Geraet_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TagCodeVergabe" (
|
||||
"tagCode" TEXT NOT NULL,
|
||||
"vergebenAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TagCodeVergabe_pkey" PRIMARY KEY ("tagCode")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GeraetetypVorlage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"geraeteTyp" "GeraeteTyp" NOT NULL,
|
||||
"vorlageId" TEXT NOT NULL,
|
||||
"position" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "GeraetetypVorlage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Vorlage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"beschreibung" TEXT,
|
||||
"kategorie" TEXT,
|
||||
"schlagworte" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"kundeId" TEXT,
|
||||
"archiviert" BOOLEAN NOT NULL DEFAULT false,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Vorlage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VorlagenVersion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"vorlageId" TEXT NOT NULL,
|
||||
"version" INTEGER NOT NULL,
|
||||
"status" "VersionStatus" NOT NULL DEFAULT 'ENTWURF',
|
||||
"aenderungsnotiz" TEXT,
|
||||
"veroeffentlichtAm" TIMESTAMP(3),
|
||||
"veroeffentlichtVonId" TEXT,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "VorlagenVersion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VorlagenVariable" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"schluessel" TEXT NOT NULL,
|
||||
"beschriftung" TEXT NOT NULL,
|
||||
"typ" "VariablenTyp" NOT NULL DEFAULT 'TEXT',
|
||||
"optionen" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"pflicht" BOOLEAN NOT NULL DEFAULT false,
|
||||
"position" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "VorlagenVariable_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VorlagenAbschnitt" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"titel" TEXT NOT NULL,
|
||||
"position" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "VorlagenAbschnitt_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VorlagenSchritt" (
|
||||
"id" TEXT NOT NULL,
|
||||
"abschnittId" TEXT NOT NULL,
|
||||
"position" INTEGER NOT NULL DEFAULT 0,
|
||||
"titel" TEXT NOT NULL,
|
||||
"hilfetext" TEXT,
|
||||
"typ" "SchrittTyp" NOT NULL DEFAULT 'HAKEN',
|
||||
"pflicht" BOOLEAN NOT NULL DEFAULT false,
|
||||
"konfig" JSONB NOT NULL DEFAULT '{}',
|
||||
"dokuUrl" TEXT,
|
||||
|
||||
CONSTRAINT "VorlagenSchritt_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Lauf" (
|
||||
"id" TEXT NOT NULL,
|
||||
"vorlagenVersionId" TEXT NOT NULL,
|
||||
"titel" TEXT NOT NULL,
|
||||
"kundeId" TEXT NOT NULL,
|
||||
"mitarbeiterId" TEXT,
|
||||
"mitarbeiterName" TEXT,
|
||||
"geraetId" TEXT,
|
||||
"abteilungId" TEXT,
|
||||
"ticket" TEXT,
|
||||
"variablen" JSONB NOT NULL DEFAULT '{}',
|
||||
"status" "LaufStatus" NOT NULL DEFAULT 'OFFEN',
|
||||
"zugewiesenAnId" TEXT,
|
||||
"faelligAm" TIMESTAMP(3),
|
||||
"erstelltVonId" TEXT,
|
||||
"gestartetAm" TIMESTAMP(3),
|
||||
"abgeschlossenAm" TIMESTAMP(3),
|
||||
"abgeschlossenVonId" TEXT,
|
||||
"archiviertAm" TIMESTAMP(3),
|
||||
"archiviertVonId" TEXT,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Lauf_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Schrittergebnis" (
|
||||
"id" TEXT NOT NULL,
|
||||
"laufId" TEXT NOT NULL,
|
||||
"vorlagenSchrittId" TEXT NOT NULL,
|
||||
"zustand" "SchrittZustand" NOT NULL DEFAULT 'OFFEN',
|
||||
"wert" JSONB,
|
||||
"notiz" TEXT,
|
||||
"aktualisiertVonId" TEXT,
|
||||
"aktualisiertAm" TIMESTAMP(3) NOT NULL,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Schrittergebnis_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Kommentar" (
|
||||
"id" TEXT NOT NULL,
|
||||
"laufId" TEXT NOT NULL,
|
||||
"schrittergebnisId" TEXT,
|
||||
"benutzerId" TEXT NOT NULL,
|
||||
"rumpf" TEXT NOT NULL,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Kommentar_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Protokoll" (
|
||||
"id" TEXT NOT NULL,
|
||||
"laufId" TEXT,
|
||||
"entitaet" TEXT NOT NULL,
|
||||
"entitaetId" TEXT NOT NULL,
|
||||
"aktion" TEXT NOT NULL,
|
||||
"benutzerId" TEXT,
|
||||
"vorher" JSONB,
|
||||
"nachher" JSONB,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Protokoll_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AbgleichSperre" (
|
||||
"schluessel" TEXT NOT NULL,
|
||||
"gehaltenVon" TEXT NOT NULL,
|
||||
"gehaltenSeit" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"laeuftAbAm" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AbgleichSperre_pkey" PRIMARY KEY ("schluessel")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Kunde_kuerzel_key" ON "Kunde"("kuerzel");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Kunde_archiviert_idx" ON "Kunde"("archiviert");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Benutzer_email_key" ON "Benutzer"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Benutzer_oidcSub_key" ON "Benutzer"("oidcSub");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Benutzer_aktiv_idx" ON "Benutzer"("aktiv");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Benutzer_rolle_idx" ON "Benutzer"("rolle");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "BenutzerKunde_kundeId_idx" ON "BenutzerKunde"("kundeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Verzeichnisquelle_kundeId_idx" ON "Verzeichnisquelle"("kundeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Verzeichnisquelle_aktiv_idx" ON "Verzeichnisquelle"("aktiv");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Mitarbeiter_kundeId_aktiv_idx" ON "Mitarbeiter"("kundeId", "aktiv");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Mitarbeiter_abteilung_idx" ON "Mitarbeiter"("abteilung");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Mitarbeiter_upn_idx" ON "Mitarbeiter"("upn");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Mitarbeiter_sam_idx" ON "Mitarbeiter"("sam");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Mitarbeiter_quelleId_externeId_key" ON "Mitarbeiter"("quelleId", "externeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Mitarbeiter_id_kundeId_key" ON "Mitarbeiter"("id", "kundeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Abteilung_kundeId_archiviert_idx" ON "Abteilung"("kundeId", "archiviert");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Abteilung_kundeId_name_key" ON "Abteilung"("kundeId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Abteilung_id_kundeId_key" ON "Abteilung"("id", "kundeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Geraet_tagCode_key" ON "Geraet"("tagCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Geraet_kundeId_archiviert_idx" ON "Geraet"("kundeId", "archiviert");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Geraet_mitarbeiterId_idx" ON "Geraet"("mitarbeiterId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Geraet_typ_idx" ON "Geraet"("typ");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Geraet_registriertAm_idx" ON "Geraet"("registriertAm");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Geraet_id_kundeId_key" ON "Geraet"("id", "kundeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GeraetetypVorlage_geraeteTyp_position_idx" ON "GeraetetypVorlage"("geraeteTyp", "position");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GeraetetypVorlage_geraeteTyp_vorlageId_key" ON "GeraetetypVorlage"("geraeteTyp", "vorlageId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Vorlage_archiviert_idx" ON "Vorlage"("archiviert");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Vorlage_kategorie_idx" ON "Vorlage"("kategorie");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Vorlage_kundeId_idx" ON "Vorlage"("kundeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VorlagenVersion_status_idx" ON "VorlagenVersion"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VorlagenVersion_vorlageId_version_key" ON "VorlagenVersion"("vorlageId", "version");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VorlagenVariable_versionId_position_idx" ON "VorlagenVariable"("versionId", "position");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VorlagenVariable_versionId_schluessel_key" ON "VorlagenVariable"("versionId", "schluessel");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VorlagenAbschnitt_versionId_position_idx" ON "VorlagenAbschnitt"("versionId", "position");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VorlagenSchritt_abschnittId_position_idx" ON "VorlagenSchritt"("abschnittId", "position");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_kundeId_status_idx" ON "Lauf"("kundeId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_geraetId_status_idx" ON "Lauf"("geraetId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_mitarbeiterId_idx" ON "Lauf"("mitarbeiterId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_zugewiesenAnId_status_idx" ON "Lauf"("zugewiesenAnId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_status_faelligAm_idx" ON "Lauf"("status", "faelligAm");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_vorlagenVersionId_idx" ON "Lauf"("vorlagenVersionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lauf_abteilungId_idx" ON "Lauf"("abteilungId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Schrittergebnis_laufId_zustand_idx" ON "Schrittergebnis"("laufId", "zustand");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Schrittergebnis_laufId_vorlagenSchrittId_key" ON "Schrittergebnis"("laufId", "vorlagenSchrittId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Kommentar_laufId_erstelltAm_idx" ON "Kommentar"("laufId", "erstelltAm");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Kommentar_schrittergebnisId_idx" ON "Kommentar"("schrittergebnisId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Protokoll_laufId_erstelltAm_idx" ON "Protokoll"("laufId", "erstelltAm");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Protokoll_entitaet_entitaetId_idx" ON "Protokoll"("entitaet", "entitaetId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Protokoll_erstelltAm_idx" ON "Protokoll"("erstelltAm");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AbgleichSperre_laeuftAbAm_idx" ON "AbgleichSperre"("laeuftAbAm");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BenutzerKunde" ADD CONSTRAINT "BenutzerKunde_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "Benutzer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BenutzerKunde" ADD CONSTRAINT "BenutzerKunde_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Verzeichnisquelle" ADD CONSTRAINT "Verzeichnisquelle_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Mitarbeiter" ADD CONSTRAINT "Mitarbeiter_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Mitarbeiter" ADD CONSTRAINT "Mitarbeiter_quelleId_fkey" FOREIGN KEY ("quelleId") REFERENCES "Verzeichnisquelle"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Abteilung" ADD CONSTRAINT "Abteilung_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Geraet" ADD CONSTRAINT "Geraet_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Geraet" ADD CONSTRAINT "Geraet_mitarbeiterId_fkey" FOREIGN KEY ("mitarbeiterId") REFERENCES "Mitarbeiter"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "GeraetetypVorlage" ADD CONSTRAINT "GeraetetypVorlage_vorlageId_fkey" FOREIGN KEY ("vorlageId") REFERENCES "Vorlage"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Vorlage" ADD CONSTRAINT "Vorlage_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VorlagenVersion" ADD CONSTRAINT "VorlagenVersion_vorlageId_fkey" FOREIGN KEY ("vorlageId") REFERENCES "Vorlage"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VorlagenVersion" ADD CONSTRAINT "VorlagenVersion_veroeffentlichtVonId_fkey" FOREIGN KEY ("veroeffentlichtVonId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VorlagenVariable" ADD CONSTRAINT "VorlagenVariable_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "VorlagenVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VorlagenAbschnitt" ADD CONSTRAINT "VorlagenAbschnitt_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "VorlagenVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VorlagenSchritt" ADD CONSTRAINT "VorlagenSchritt_abschnittId_fkey" FOREIGN KEY ("abschnittId") REFERENCES "VorlagenAbschnitt"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_vorlagenVersionId_fkey" FOREIGN KEY ("vorlagenVersionId") REFERENCES "VorlagenVersion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_kundeId_fkey" FOREIGN KEY ("kundeId") REFERENCES "Kunde"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_mitarbeiterId_fkey" FOREIGN KEY ("mitarbeiterId") REFERENCES "Mitarbeiter"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_geraetId_fkey" FOREIGN KEY ("geraetId") REFERENCES "Geraet"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_abteilungId_fkey" FOREIGN KEY ("abteilungId") REFERENCES "Abteilung"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_erstelltVonId_fkey" FOREIGN KEY ("erstelltVonId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_zugewiesenAnId_fkey" FOREIGN KEY ("zugewiesenAnId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_abgeschlossenVonId_fkey" FOREIGN KEY ("abgeschlossenVonId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lauf" ADD CONSTRAINT "Lauf_archiviertVonId_fkey" FOREIGN KEY ("archiviertVonId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Schrittergebnis" ADD CONSTRAINT "Schrittergebnis_laufId_fkey" FOREIGN KEY ("laufId") REFERENCES "Lauf"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Schrittergebnis" ADD CONSTRAINT "Schrittergebnis_vorlagenSchrittId_fkey" FOREIGN KEY ("vorlagenSchrittId") REFERENCES "VorlagenSchritt"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Schrittergebnis" ADD CONSTRAINT "Schrittergebnis_aktualisiertVonId_fkey" FOREIGN KEY ("aktualisiertVonId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Kommentar" ADD CONSTRAINT "Kommentar_laufId_fkey" FOREIGN KEY ("laufId") REFERENCES "Lauf"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Kommentar" ADD CONSTRAINT "Kommentar_schrittergebnisId_fkey" FOREIGN KEY ("schrittergebnisId") REFERENCES "Schrittergebnis"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Kommentar" ADD CONSTRAINT "Kommentar_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Protokoll" ADD CONSTRAINT "Protokoll_laufId_fkey" FOREIGN KEY ("laufId") REFERENCES "Lauf"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Protokoll" ADD CONSTRAINT "Protokoll_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "Benutzer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Regeln, die sich im Prisma-Schema nicht ausdruecken lassen.
|
||||
--
|
||||
-- Sie gehoeren in die Datenbank und nicht nur in die Service-Schicht, damit sie
|
||||
-- auch dann gelten, wenn jemand an ihr vorbei schreibt: per Migration, per psql,
|
||||
-- aus einem Skript oder aus einem spaeteren Connector.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- ---- Archiv-Volltextsuche -------------------------------------------------
|
||||
-- Gesucht wird mit ILIKE '%...%' ueber Titel, Notizen, Werte und Kommentare
|
||||
-- (#015). Ohne Trigramm-Index ist jede dieser Spalten ein Full Scan.
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
CREATE INDEX "Lauf_titel_trgm_idx" ON "Lauf" USING gin ("titel" gin_trgm_ops);
|
||||
CREATE INDEX "Schrittergebnis_notiz_trgm_idx" ON "Schrittergebnis" USING gin ("notiz" gin_trgm_ops);
|
||||
CREATE INDEX "Kommentar_rumpf_trgm_idx" ON "Kommentar" USING gin ("rumpf" gin_trgm_ops);
|
||||
-- Die erfassten Werte liegen als Json vor; gesucht wird ueber ihre Textform.
|
||||
CREATE INDEX "Schrittergebnis_wert_trgm_idx"
|
||||
ON "Schrittergebnis" USING gin ((("wert")::text) gin_trgm_ops);
|
||||
|
||||
-- ---- Einmaligkeiten --------------------------------------------------------
|
||||
-- Es gibt hoechstens eine eigene Organisation. Ein gewoehnlicher eindeutiger
|
||||
-- Index wuerde auch die vielen false-Zeilen erfassen, deshalb partiell.
|
||||
CREATE UNIQUE INDEX "Kunde_eigene_organisation_einmalig"
|
||||
ON "Kunde" ("istEigeneOrganisation")
|
||||
WHERE "istEigeneOrganisation";
|
||||
|
||||
-- Je Vorlage hoechstens ein Entwurf. Veroeffentlichte Versionen sind
|
||||
-- unveraenderlich, ein zweiter Entwurf haette also keine eindeutige Grundlage.
|
||||
CREATE UNIQUE INDEX "VorlagenVersion_ein_entwurf"
|
||||
ON "VorlagenVersion" ("vorlageId")
|
||||
WHERE "status" = 'ENTWURF';
|
||||
|
||||
-- ---- Formen ----------------------------------------------------------------
|
||||
-- Das Kuerzel steht auf Etiketten und in der Hostname-Regel.
|
||||
ALTER TABLE "Kunde"
|
||||
ADD CONSTRAINT "Kunde_kuerzel_form" CHECK ("kuerzel" ~ '^[A-Z0-9]{2,6}$');
|
||||
|
||||
-- Der tagCode wird nie geaendert und nie wiederverwendet. Die Form wird schon
|
||||
-- beim Erzeugen geprueft; hier steht sie als letzte Sicherung.
|
||||
ALTER TABLE "Geraet"
|
||||
ADD CONSTRAINT "Geraet_tagcode_form"
|
||||
CHECK ("tagCode" ~ '^LZ-[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{6}$');
|
||||
|
||||
ALTER TABLE "TagCodeVergabe"
|
||||
ADD CONSTRAINT "TagCodeVergabe_form"
|
||||
CHECK ("tagCode" ~ '^LZ-[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{6}$');
|
||||
|
||||
-- Ein verschluesseltes Geheimnis traegt immer ein Versionspraefix (siehe
|
||||
-- src/lib/geheimnisse.ts). Damit faellt ein versehentlich im Klartext
|
||||
-- geschriebenes Bind-Passwort sofort auf.
|
||||
ALTER TABLE "Verzeichnisquelle"
|
||||
ADD CONSTRAINT "Verzeichnisquelle_bindpasswort_verschluesselt"
|
||||
CHECK ("bindPasswortVerschluesselt" ~ '^v[0-9]+\.');
|
||||
|
||||
-- Einstellung ist eine einzelne Zeile. Ohne diese Bedingung koennte ein zweiter
|
||||
-- Datensatz entstehen und die Anwendung haette zwei Betriebsmodi.
|
||||
ALTER TABLE "Einstellung"
|
||||
ADD CONSTRAINT "Einstellung_einzelne_zeile" CHECK ("id" = 'global');
|
||||
|
||||
-- ---- Fachliche Widerspruchsfreiheit ---------------------------------------
|
||||
-- Ein Geraet ohne registriertAm ist ein vorgedruckter, noch nicht zugeordneter
|
||||
-- Tag – der darf leer sein. Sobald es registriert ist, braucht es einen
|
||||
-- Hostname oder eine Bezeichnung, sonst laesst es sich nicht benennen.
|
||||
ALTER TABLE "Geraet"
|
||||
ADD CONSTRAINT "Geraet_registriert_braucht_namen" CHECK (
|
||||
"registriertAm" IS NULL
|
||||
OR COALESCE(NULLIF(TRIM("hostname"), ''), NULLIF(TRIM("bezeichnung"), '')) IS NOT NULL
|
||||
);
|
||||
|
||||
-- Ein Lauf haengt entweder an einem Mitarbeiter aus dem Verzeichnis oder traegt
|
||||
-- einen Freitextnamen – beides zugleich waere widerspruechlich.
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_mitarbeiter_eindeutig" CHECK (
|
||||
"mitarbeiterId" IS NULL OR "mitarbeiterName" IS NULL
|
||||
);
|
||||
|
||||
-- Status und Zeitstempel duerfen nicht auseinanderlaufen, sonst zeigt das Archiv
|
||||
-- einen abgeschlossenen Lauf ohne Abschlusszeitpunkt.
|
||||
-- Der veroeffentlichende Benutzer bleibt bewusst optional: der Seed
|
||||
-- veroeffentlicht die sieben Vorlagen ohne angemeldeten Menschen.
|
||||
ALTER TABLE "VorlagenVersion"
|
||||
ADD CONSTRAINT "VorlagenVersion_veroeffentlicht_mit_zeitpunkt" CHECK (
|
||||
"status" <> 'VEROEFFENTLICHT' OR "veroeffentlichtAm" IS NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_begonnen_mit_zeitpunkt" CHECK (
|
||||
"status" = 'OFFEN' OR "gestartetAm" IS NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_abgeschlossen_mit_zeitpunkt" CHECK (
|
||||
"status" NOT IN ('ABGESCHLOSSEN', 'ARCHIVIERT') OR "abgeschlossenAm" IS NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_archiviert_mit_zeitpunkt" CHECK (
|
||||
"status" <> 'ARCHIVIERT' OR "archiviertAm" IS NOT NULL
|
||||
);
|
||||
|
||||
-- NICHT_ZUTREFFEND und FEHLGESCHLAGEN verlangen eine Begruendung. Die
|
||||
-- Service-Schicht prueft das ebenfalls, nur mit verstaendlicherer Meldung.
|
||||
ALTER TABLE "Schrittergebnis"
|
||||
ADD CONSTRAINT "Schrittergebnis_begruendung_noetig" CHECK (
|
||||
"zustand" NOT IN ('NICHT_ZUTREFFEND', 'FEHLGESCHLAGEN')
|
||||
OR NULLIF(TRIM("notiz"), '') IS NOT NULL
|
||||
);
|
||||
|
||||
-- ---- Mandantengrenze -------------------------------------------------------
|
||||
-- Die Trennung nach Kunden ist eine Sicherheitsgrenze (siehe
|
||||
-- docs/architektur.md) und soll nicht allein an kundeErmitteln() haengen. Die
|
||||
-- zusammengesetzten Fremdschluessel machen es unmoeglich, dass ein Lauf ein
|
||||
-- Geraet, eine Person oder eine Abteilung eines fremden Kunden waehlt.
|
||||
--
|
||||
-- MATCH SIMPLE ist hier genau richtig: ist die optionale Spalte NULL, greift der
|
||||
-- Fremdschluessel nicht – ein Lauf ohne Geraet bleibt also erlaubt.
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_geraet_gleicher_kunde"
|
||||
FOREIGN KEY ("geraetId", "kundeId") REFERENCES "Geraet" ("id", "kundeId")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_mitarbeiter_gleicher_kunde"
|
||||
FOREIGN KEY ("mitarbeiterId", "kundeId") REFERENCES "Mitarbeiter" ("id", "kundeId")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "Lauf"
|
||||
ADD CONSTRAINT "Lauf_abteilung_gleicher_kunde"
|
||||
FOREIGN KEY ("abteilungId", "kundeId") REFERENCES "Abteilung" ("id", "kundeId")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- Auch der Besitzer eines Geraets muss zum selben Kunden gehoeren.
|
||||
ALTER TABLE "Geraet"
|
||||
ADD CONSTRAINT "Geraet_besitzer_gleicher_kunde"
|
||||
FOREIGN KEY ("mitarbeiterId", "kundeId") REFERENCES "Mitarbeiter" ("id", "kundeId")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- ---- Zeitstempel ohne Prisma ----------------------------------------------
|
||||
-- @updatedAt setzt Prisma im Client. Wer per psql, Migration oder Skript
|
||||
-- schreibt, haette sonst eine NOT-NULL-Verletzung – genau die Wege, die diese
|
||||
-- Datei oben ausdruecklich einkalkuliert.
|
||||
DO $$
|
||||
DECLARE
|
||||
z record;
|
||||
BEGIN
|
||||
FOR z IN
|
||||
SELECT table_name, column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND column_name IN ('geaendertAm', 'aktualisiertAm')
|
||||
LOOP
|
||||
EXECUTE format(
|
||||
'ALTER TABLE %I ALTER COLUMN %I SET DEFAULT CURRENT_TIMESTAMP',
|
||||
z.table_name, z.column_name
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user