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
56 lines
2.2 KiB
PL/PgSQL
56 lines
2.2 KiB
PL/PgSQL
-- =============================================================================
|
|
-- 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);
|