feat(bausteine): Abschnitte, die mehrere Vorlagen teilen (#57)
This commit was merged in pull request #57.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die
|
||||
-- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes
|
||||
-- (Archivsuche), die partiellen eindeutigen Indizes und die Trigger auf
|
||||
-- veroeffentlichten Versionen: es kennt sie nicht, weil sie sich im Schema nicht
|
||||
-- ausdruecken lassen. Diese Loeschbefehle sind bewusst gestrichen.
|
||||
--
|
||||
-- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind –
|
||||
-- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Wiederverwendbare Bausteine (#030).
|
||||
|
||||
CREATE TABLE "Baustein" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"beschreibung" TEXT,
|
||||
"archiviert" BOOLEAN NOT NULL DEFAULT false,
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "Baustein_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "Baustein_name_key" ON "Baustein"("name");
|
||||
CREATE INDEX "Baustein_archiviert_idx" ON "Baustein"("archiviert");
|
||||
|
||||
CREATE TABLE "BausteinVersion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bausteinId" TEXT NOT NULL,
|
||||
"version" INTEGER NOT NULL,
|
||||
"status" "VersionStatus" NOT NULL DEFAULT 'ENTWURF',
|
||||
"titel" TEXT NOT NULL,
|
||||
"aenderungsnotiz" TEXT,
|
||||
"veroeffentlichtAm" TIMESTAMP(3),
|
||||
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"geaendertAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "BausteinVersion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "BausteinVersion_bausteinId_version_key"
|
||||
ON "BausteinVersion"("bausteinId", "version");
|
||||
CREATE INDEX "BausteinVersion_bausteinId_status_idx"
|
||||
ON "BausteinVersion"("bausteinId", "status");
|
||||
|
||||
CREATE TABLE "BausteinSchritt" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionId" 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 "BausteinSchritt_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "BausteinSchritt_versionId_position_idx"
|
||||
ON "BausteinSchritt"("versionId", "position");
|
||||
|
||||
ALTER TABLE "BausteinVersion"
|
||||
ADD CONSTRAINT "BausteinVersion_bausteinId_fkey"
|
||||
FOREIGN KEY ("bausteinId") REFERENCES "Baustein"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "BausteinSchritt"
|
||||
ADD CONSTRAINT "BausteinSchritt_versionId_fkey"
|
||||
FOREIGN KEY ("versionId") REFERENCES "BausteinVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Der Verweis am Abschnitt. RESTRICT, nicht CASCADE: eine Baustein-Version, auf
|
||||
-- die eine Vorlage zeigt, darf nicht unter ihr wegbrechen.
|
||||
ALTER TABLE "VorlagenAbschnitt"
|
||||
ADD COLUMN "bausteinVersionId" TEXT;
|
||||
|
||||
CREATE INDEX "VorlagenAbschnitt_bausteinVersionId_idx"
|
||||
ON "VorlagenAbschnitt"("bausteinVersionId");
|
||||
|
||||
ALTER TABLE "VorlagenAbschnitt"
|
||||
ADD CONSTRAINT "VorlagenAbschnitt_bausteinVersionId_fkey"
|
||||
FOREIGN KEY ("bausteinVersionId") REFERENCES "BausteinVersion"("id")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Dieselben Garantien wie bei Vorlagen, und zwar in der Datenbank.
|
||||
--
|
||||
-- Ein Baustein ist eine Quelle fuer Schritte, die in Laeufe wandern. Stuende
|
||||
-- seine Unveraenderlichkeit nur im Dienst, liesse sich die Nachvollziehbarkeit
|
||||
-- des Archivs ueber eine Baustein-Aenderung aushebeln.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Je Baustein hoechstens ein Entwurf.
|
||||
CREATE UNIQUE INDEX "BausteinVersion_ein_entwurf"
|
||||
ON "BausteinVersion" ("bausteinId")
|
||||
WHERE "status" = 'ENTWURF';
|
||||
|
||||
-- Eine veroeffentlichte Baustein-Version ist unveraenderlich.
|
||||
CREATE OR REPLACE FUNCTION laufzettel_baustein_unveraenderlich() RETURNS trigger AS $$
|
||||
DECLARE
|
||||
betroffene_version TEXT;
|
||||
aktueller_status TEXT;
|
||||
BEGIN
|
||||
betroffene_version := COALESCE(NEW."versionId", OLD."versionId");
|
||||
SELECT status::TEXT INTO aktueller_status
|
||||
FROM "BausteinVersion" WHERE id = betroffene_version;
|
||||
|
||||
IF aktueller_status = 'VEROEFFENTLICHT' THEN
|
||||
RAISE EXCEPTION
|
||||
'Baustein-Version % ist veroeffentlicht und unveraenderlich (%).',
|
||||
betroffene_version, TG_TABLE_NAME
|
||||
USING ERRCODE = 'integrity_constraint_violation';
|
||||
END IF;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER "BausteinSchritt_unveraenderlich"
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON "BausteinSchritt"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_baustein_unveraenderlich();
|
||||
|
||||
-- Der Weg zurueck waere die Hintertuer, durch die sich das Obige aushebeln
|
||||
-- liesse. Dieselbe Regel wie bei VorlagenVersion.
|
||||
CREATE OR REPLACE FUNCTION laufzettel_baustein_status_einbahn() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF OLD.status = 'VEROEFFENTLICHT' AND NEW.status <> 'VEROEFFENTLICHT' THEN
|
||||
RAISE EXCEPTION
|
||||
'Eine veroeffentlichte Baustein-Version laesst sich nicht wieder zum Entwurf machen.'
|
||||
USING ERRCODE = 'integrity_constraint_violation';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER "BausteinVersion_status_einbahn"
|
||||
BEFORE UPDATE ON "BausteinVersion"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_baustein_status_einbahn();
|
||||
|
||||
-- Ein Verweis zeigt nur auf eine **veroeffentlichte** Baustein-Version. Einen
|
||||
-- Entwurf einzusetzen hiesse, beim Veroeffentlichen etwas zu kopieren, das sich
|
||||
-- danach noch aendert.
|
||||
CREATE OR REPLACE FUNCTION laufzettel_baustein_verweis_veroeffentlicht() RETURNS trigger AS $$
|
||||
DECLARE
|
||||
verwiesener_status TEXT;
|
||||
BEGIN
|
||||
IF NEW."bausteinVersionId" IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
SELECT status::TEXT INTO verwiesener_status
|
||||
FROM "BausteinVersion" WHERE id = NEW."bausteinVersionId";
|
||||
|
||||
IF verwiesener_status <> 'VEROEFFENTLICHT' THEN
|
||||
RAISE EXCEPTION
|
||||
'Ein Abschnitt kann nur auf eine veroeffentlichte Baustein-Version verweisen.'
|
||||
USING ERRCODE = 'integrity_constraint_violation';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER "VorlagenAbschnitt_baustein_veroeffentlicht"
|
||||
BEFORE INSERT OR UPDATE ON "VorlagenAbschnitt"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_baustein_verweis_veroeffentlicht();
|
||||
+76
-6
@@ -464,13 +464,83 @@ model VorlagenVariable {
|
||||
}
|
||||
|
||||
model VorlagenAbschnitt {
|
||||
id String @id @default(cuid())
|
||||
versionId String
|
||||
titel String
|
||||
position Int @default(0)
|
||||
id String @id @default(cuid())
|
||||
versionId String
|
||||
titel String
|
||||
position Int @default(0)
|
||||
/// Verweis auf einen wiederverwendbaren Baustein (#030).
|
||||
///
|
||||
/// In einem **Entwurf** heisst das: beim Veroeffentlichen hier die Schritte des
|
||||
/// Bausteins einsetzen. In einer **veroeffentlichten** Version heisst dasselbe
|
||||
/// Feld: diese Schritte stammen von dort — es ist dann nur noch Herkunft.
|
||||
/// Welche der beiden Lesarten gilt, sagt der Status der Version; ein zweites
|
||||
/// Feld dafuer waere eine zweite Wahrheit.
|
||||
bausteinVersionId String?
|
||||
|
||||
version VorlagenVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
|
||||
schritte VorlagenSchritt[]
|
||||
version VorlagenVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
|
||||
bausteinVersion BausteinVersion? @relation(fields: [bausteinVersionId], references: [id], onDelete: Restrict)
|
||||
schritte VorlagenSchritt[]
|
||||
|
||||
@@index([versionId, position])
|
||||
@@index([bausteinVersionId])
|
||||
}
|
||||
|
||||
// ---- Bausteine -------------------------------------------------------------
|
||||
|
||||
/// Ein Abschnitt, den mehrere Vorlagen teilen (#030).
|
||||
///
|
||||
/// Versioniert nach denselben Regeln wie eine Vorlage: hoechstens ein Entwurf,
|
||||
/// eine veroeffentlichte Version wird nie veraendert. Beim Veroeffentlichen
|
||||
/// einer Vorlage wird der Baustein **tief kopiert** — ein Lauf sieht damit
|
||||
/// weiterhin konkrete Schritte, und Schrittergebnis, Druckansicht und Archiv
|
||||
/// muessen nichts lernen.
|
||||
model Baustein {
|
||||
id String @id @default(cuid())
|
||||
/// Eindeutig: der Name ist, wonach ein Redakteur ihn sucht.
|
||||
name String @unique
|
||||
beschreibung String?
|
||||
archiviert Boolean @default(false)
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @default(now()) @updatedAt
|
||||
|
||||
versionen BausteinVersion[]
|
||||
|
||||
@@index([archiviert])
|
||||
}
|
||||
|
||||
model BausteinVersion {
|
||||
id String @id @default(cuid())
|
||||
bausteinId String
|
||||
version Int
|
||||
status VersionStatus @default(ENTWURF)
|
||||
/// Titel des Abschnitts, der daraus entsteht. Steht an der Version und nicht
|
||||
/// am Baustein, damit er sich aendern laesst, ohne alte Vorlagen umzuschreiben.
|
||||
titel String
|
||||
aenderungsnotiz String?
|
||||
veroeffentlichtAm DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @default(now()) @updatedAt
|
||||
|
||||
baustein Baustein @relation(fields: [bausteinId], references: [id], onDelete: Cascade)
|
||||
schritte BausteinSchritt[]
|
||||
verwendungen VorlagenAbschnitt[]
|
||||
|
||||
@@unique([bausteinId, version])
|
||||
@@index([bausteinId, status])
|
||||
}
|
||||
|
||||
model BausteinSchritt {
|
||||
id String @id @default(cuid())
|
||||
versionId String
|
||||
position Int @default(0)
|
||||
titel String
|
||||
hilfetext String?
|
||||
typ SchrittTyp @default(HAKEN)
|
||||
pflicht Boolean @default(false)
|
||||
konfig Json @default("{}")
|
||||
dokuUrl String?
|
||||
|
||||
version BausteinVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([versionId, position])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user