feat: konfigurierbaren Freigabe-Workflow mit Genehmiger-Rollen einführen

Mandanten können jetzt selbst festlegen, wer bei welcher abgeleiteten
Bedingung (Anforderung/Einstufung/Datenklasse) zusätzlich zur
Fachebene-Entscheidung zustimmen muss (z. B. Datenschutzbeauftragter,
Geschäftsführung) - Genehmiger-Rollen sind orthogonal zu den fünf
bestehenden Zugriffsrollen. Alle ausgelösten Rollen müssen zustimmen,
eine Ablehnung kippt kaskadierend den gesamten Antrag. Mandanten ohne
Freigabe-Regeln (aktuell alle) sind unverändert vom alten Direktpfad
betroffen, siehe TestOhneFreigabeRegelnVerhaeltSichWieVorher.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
noroot
2026-08-31 21:42:28 +02:00
parent 9e2c7f92ff
commit f729e5ae48
16 changed files with 2107 additions and 36 deletions

128
CLAUDE.md
View File

@@ -478,6 +478,118 @@ das Register ist eine Genehmigungsliste, kein vollständiges Antragslog
---
## Freigabe-Workflow (konfigurierbare Mehrfach-Genehmigung, 2026-08-31)
**Bewusste Abkehr von einer dokumentierten v1-Ausschlussentscheidung.**
Diese CLAUDE.md schloss "konfigurierbare Rollen mit eigener Oberfläche"
und "Workflow-Designer" ursprünglich explizit für v1 aus (siehe "Nicht
bauen (v1)" unten). Der Nutzer hat das am 2026-08-31 bewusst und nach
Rückfrage aufgehoben ("Wir sollten das System schon perfekt bauen! Daher
auch gerne komplex") — Auslöser war die Frage, ob ein Datenschutz-
beauftragter und/oder die Geschäftsführung zusätzlich in die Entscheidung
eingebunden werden müssen. Statt DSB/GF fest zu verdrahten, können
Mandanten jetzt selbst definieren, wer bei welcher abgeleiteten
Bedingung zusätzlich zustimmen muss.
**Datenmodell (Migration 0017):** vier neue Tabellen, orthogonal zu den
fünf bestehenden Zugriffsrollen (`mitarbeiter`/`verantwortlicher`/
`pruefer`/`admin`/`betreiber` bleiben unverändert):
- `genehmiger_rolle` — eine mandantendefinierte Freigabe-Funktion (z. B.
"Datenschutzbeauftragter", "Geschäftsführung"). Kein Zugriffsrecht,
nur eine Freigabe-Zuständigkeit.
- `nutzer_genehmiger_rolle` — Zuordnung Person↔Genehmiger-Rolle,
many-to-many. Eine Person kann null, eine oder mehrere davon haben,
unabhängig von ihrer `app_user.role`.
- `freigabe_regel` — "wenn Bedingung X zutrifft, ist zusätzlich eine
Freigabe durch Genehmiger-Rolle Y nötig". Die Bedingung ist bewusst
**kein freier Regeleditor**, sondern auf die drei Vokabulare
beschränkt, die die Bewertungslogik ohnehin schon berechnet:
Anforderungs-ID (z. B. `dsfa_erforderlich`), Einstufungs-ID (z. B.
`hochrisiko`) oder Datenklasse-ID (z. B. `besondere_kategorie`).
Kompromiss zwischen "konfigurierbar" (Nutzeranforderung) und
"buildbar/auditierbar" (jede mögliche Bedingung ist bereits eine
geprüfte, bekannte Ableitung, keine beliebige Freitext-Regel).
- `freigabeschritt` — pro Antrag ein Datensatz je ausgelöster
Freigabe-Regel, Status `ausstehend`/`genehmigt`/`abgelehnt`.
`antrag.status` hat einen neuen Zwischenwert `wartet_auf_freigabe`
zwischen `eingereicht` und `entschieden` (CHECK-Constraint erweitert).
**Ablauf:** `handleFallEntscheiden` (`internal/web/fachebene_handlers.go`)
prüft nach einer Genehmigung/Genehmigung-mit-Auflagen, ob die Bewertung
mindestens eine `freigabe_regel` des Mandanten auslöst
(`triggeredGenehmigerRollen`). Falls ja: `antrag.status` wird
`wartet_auf_freigabe` statt `entschieden`, pro ausgelöster Regel entsteht
ein `freigabeschritt`, **kein** `registereintrag` entsteht noch. Falls
keine Regel greift, läuft exakt der alte Pfad (`registriereGenehmigung`)
**verifiziert per dediziertem Test**
(`TestOhneFreigabeRegelnVerhaeltSichWieVorher`), da alle heutigen
Bestandskunden null Freigabe-Regeln haben und sich für sie nichts ändern
darf.
**Bestätigte Semantik (2026-08-31, Nutzer: "Beides stimmt!"):**
1. **Alle** ausgelösten Genehmiger-Rollen müssen zustimmen, nicht nur
eine von mehreren — geprüft durch erneutes `ListFreigabeschritteForAntrag`
nach jeder Einzelentscheidung.
2. **Eine Ablehnung kippt den gesamten Antrag**, geht nicht zurück an
die Fachebene zur Neuentscheidung. `KaskadiereAblehnung` setzt dabei
automatisch alle anderen noch `ausstehend`en Freigabeschritte
desselben Antrags ebenfalls auf `abgelehnt` — sonst würde ein
Freigabeschritt in der Liste einer anderen Person ewig unbearbeitet
hängen bleiben, obwohl der Antrag längst entschieden ist.
Eine Ablehnung (ob direkt oder per Kaskade) mutiert **nicht** die
ursprüngliche `genehmigt`-Zeile — `entscheidung` bleibt append-only wie
bisher. Stattdessen entsteht eine neue `entscheidung`-Zeile mit
`Entscheidung: "abgelehnt"`; `GetLatestEntscheidungForAntrag` (bereits
bestehender "letzte Zeile gewinnt"-Helfer) zeigt den Override überall
dort, wo er ohnehin schon verwendet wird — keine weitere Codeänderung
nötig, um die Überschreibung sichtbar zu machen.
**Frontend** (`internal/web/freigabe_handlers.go` + drei neue Templates):
`GET/POST /verwaltung/genehmiger-rollen` (Admin: Rolle anlegen, Mitglieder
zuordnen/entfernen — Formular nutzt einen einzelnen `<select>` mit drei
`<optgroup>`s für die drei Bedingungstypen, kein abhängiges Dropdown,
kein JavaScript), `GET/POST /verwaltung/freigabe-regeln` (Admin: Regel
anlegen/löschen), `GET /freigaben` + `POST /freigaben/{id}/entscheiden`
(jeder eingeloggte Nicht-Betreiber mit zugeordneter Genehmiger-Rolle:
eigene offene Freigaben sehen und entscheiden, mit Pflicht-Kommentarfeld
bei Ablehnung). Fall-Detail (`fall_detail.html`) zeigt die
Freigabeschritt-Historie mit Ampel-Badge **unabhängig vom aktuellen
Antragsstatus** — anfangs fälschlich auf `wartet_auf_freigabe`
beschränkt gebaut, beim Live-Verifizieren aufgefallen: sobald der letzte
Freigebende zustimmt und der Antrag auf `entschieden` springt, verschwand
die komplette Freigabehistorie aus der Ansicht, was der Grundregel
widerspricht, dass eine Entscheidung nie ohne vollständige Herleitung
gezeigt wird. Der Hinweistext "Genehmigung erst endgültig, wenn alle
erteilt sind" erscheint entsprechend nur noch, solange der Antrag
tatsächlich noch auf Freigabe wartet.
**CSS-Bugfix, beim Live-Verifizieren gefunden (sitesweit, nicht nur
diese Seite betreffend):** `.beitraege-liste > li:not(:has(> a)) form`
hatte `flex: 0 0 auto` (kein Schrumpfen). Bei den bisherigen Listen
(Nutzer, Abteilungen) enthielten diese eingebetteten Formulare nur
Buttons, die naturgemäß schmal bleiben — die Genehmiger-Rollen-Seite ist
die erste mit einem `<select>` darin, dessen Browser-Eigenbreite
(bestimmt durch die längste Options-Beschriftung, hier E-Mail-Adressen)
den ganzen Zeilen-Container über die Mobile-Viewport-Breite hinaustrieb
(horizontales Scrollen der ganzen Seite). Fix: `max-width: 100%` auf
derselben Regel ergänzt — generisch, wirkt auf jedes künftige
`<select>` in diesem Listen-Pattern, nicht nur hier.
**Live end-to-end verifiziert** (curl gegen echten lokalen Server +
echtes Postgres, nicht nur die Go-Testsuite): Happy Path (eine Regel,
eine Rolle, Genehmigung → `wartet_auf_freigabe` → Freigabe erteilt →
`entschieden` + 1 Registereintrag) und Kaskaden-Ablehnung (zwei Regeln,
zwei Rollen, eine lehnt ab → beide Freigabeschritte `abgelehnt`, Antrag
direkt `entschieden` als `abgelehnt`, 0 Registereinträge) — beide exakt
wie spezifiziert. Chromium-Headless-Screenshots (Desktop 1440×900 +
Mobile 390×844) aller vier neuen/geänderten Seiten bestätigen zusätzlich
das responsive Layout nach dem enconf-Card-Pattern.
---
## Onboarding
**Firma (Ebene 1, öffentlich, `POST /register`):** Registrierungsformular
@@ -818,12 +930,22 @@ wiederverwendet aus dem alten Produkt), ebenso `bewertung`,
(`GET /wiedervorlage`: abgelaufene/bald ablaufende Genehmigungen und
Genehmigungen, deren Werkzeug sich seither im Katalog geändert hat
oder entfernt wurde. In-App-Liste, kein E-Mail-Versand.)
8. ~~Konfigurierbarer Freigabe-Workflow (Mehrfach-Genehmigung)~~
**erledigt** (siehe "Freigabe-Workflow" weiter oben: Genehmiger-Rollen,
Freigabe-Regeln, Freigabeschritte, Migration 0017 — bewusste Abkehr
von der ursprünglichen v1-Ausschlussentscheidung, siehe "Nicht bauen
(v1)" unten).
Nicht bauen (v1): automatische Genehmigung ohne Mensch, Erkennung
tatsächlicher Werkzeug-Nutzung, Mitarbeiterüberwachung (nichts, was
Nutzung einzelner Personen auswertet), konfigurierbare Rollen mit
eigener Oberfläche, Konzernstrukturen mit Vererbung, mandantenspezifische
Regelwerke, Workflow-Designer, SAML, Schnittstellen zu Fremdsystemen.
Nutzung einzelner Personen auswertet), Konzernstrukturen mit Vererbung,
mandantenspezifische Regelwerke, SAML, Schnittstellen zu Fremdsystemen.
~~konfigurierbare Rollen mit eigener Oberfläche~~ / ~~Workflow-Designer~~
**am 2026-08-31 bewusst aufgehoben**, siehe "Freigabe-Workflow"
weiter oben: Genehmiger-Rollen und Freigabe-Regeln sind jetzt genau das,
nur mit der Bedingung auf die drei bereits von der Bewertungslogik
berechneten Vokabulare beschränkt (kein beliebiger Regeleditor).
---