feat: Vollstaendiges Datenmodell als Prisma-Schema
Alle Fachentitaeten der Betreuung in einem Schema - bewusst komplett, damit spaetere Etappen keine Migration am Fundament brauchen. Kernentscheidungen: - Geldbetraege durchgaengig als BigInt in Cent. Eine Rechnungslegung nach § 1865 BGB darf keine Rundungsfehler erben. - Felder mit Suffix Verschluesselt tragen einen AES-256-GCM-Umschlag. Wo trotzdem gesucht werden muss (IBAN), steht ein HMAC-Blindindex daneben. - Fristen und Stichtage sind @db.Date, nicht DateTime - sonst verschiebt die Zeitzone eine Frist um einen Tag. - Frist.systemSchluessel macht die aus dem Bestellungsdatum abgeleiteten Pflichtfristen idempotent. - Genehmigung.beschlussDokumentId ist die technische Voraussetzung dafuer, dass ein Vorgang ohne Beschluss nicht auf VOLLZOGEN gesetzt werden kann. - DokumentVerknuepfung mit echten Fremdschluesseln statt loser Typ/ID- Spalte: eine Akte muss referenzsicher sein. - Wertstand und Saldostand sind Historien - Werte werden nie ueberschrieben. Dazu zwei SQL-Skripte, die Prisma nicht abbilden kann: - volltextsuche.sql: tsvector-Trigger (deutsch) mit Gewichtung A/B/C, plus Trigram-Indizes fuer unscharfe Absendersuche. - audit-schutz.sql: Trigger, die UPDATE und DELETE auf audit_log verweigern. Das Log ist Nachweis gegenueber Gericht und Miterben, also nur anhaengend - auch gegen psql. Etappe: 1 Phase: Fundament
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
-- =============================================================================
|
||||
-- Volltextsuche ueber Dokumente (deutsche Konfiguration)
|
||||
-- =============================================================================
|
||||
--
|
||||
-- Aufruf: npm run db:volltext
|
||||
--
|
||||
-- Die Spalte "suchvektor" legt Prisma als Unsupported("tsvector") an. Befuellt
|
||||
-- wird sie hier per Trigger, damit jede Aenderung an Titel, Absender,
|
||||
-- Aktenzeichen oder OCR-Text sofort in der Suche ankommt.
|
||||
--
|
||||
-- Gewichtung:
|
||||
-- A = Titel (schlaegt am staerksten durch)
|
||||
-- B = Absender, Aktenzeichen, Dateiname
|
||||
-- C = OCR-Volltext
|
||||
--
|
||||
-- Das Skript ist idempotent und kann nach jeder Migration erneut laufen.
|
||||
-- =============================================================================
|
||||
|
||||
-- Fuzzy-Suche fuer Absendernamen ("Bezirk Oberfr." findet "Bezirk Oberfranken").
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
-- Erlaubt das Entfernen von Akzenten in der Suche.
|
||||
CREATE EXTENSION IF NOT EXISTS unaccent;
|
||||
|
||||
CREATE OR REPLACE FUNCTION dokument_suchvektor_aktualisieren()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.suchvektor :=
|
||||
setweight(to_tsvector('german', coalesce(NEW.titel, '')), 'A')
|
||||
|| setweight(to_tsvector('german', coalesce(NEW.absender, '')), 'B')
|
||||
|| setweight(to_tsvector('german', coalesce(NEW.empfaenger, '')), 'B')
|
||||
|| setweight(to_tsvector('german', coalesce(NEW.aktenzeichen, '')), 'B')
|
||||
|| setweight(to_tsvector('german', coalesce(NEW.dateiname, '')), 'B')
|
||||
|| setweight(to_tsvector('german', coalesce(NEW."ocrText", '')), 'C');
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS dokument_suchvektor ON dokument;
|
||||
|
||||
CREATE TRIGGER dokument_suchvektor
|
||||
BEFORE INSERT OR UPDATE OF titel, absender, empfaenger, aktenzeichen, dateiname, "ocrText"
|
||||
ON dokument
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION dokument_suchvektor_aktualisieren();
|
||||
|
||||
-- Bestehende Zeilen einmalig nachziehen (z.B. nach einem Restore).
|
||||
UPDATE dokument SET titel = titel WHERE suchvektor IS NULL;
|
||||
|
||||
-- Trigram-Index fuer die unscharfe Suche nach Absendern.
|
||||
CREATE INDEX IF NOT EXISTS dokument_absender_trgm
|
||||
ON dokument USING gin (absender gin_trgm_ops);
|
||||
|
||||
-- Trigram-Index fuer die Vorgangssuche.
|
||||
CREATE INDEX IF NOT EXISTS vorgang_titel_trgm
|
||||
ON vorgang USING gin (titel gin_trgm_ops);
|
||||
Reference in New Issue
Block a user