Fachdokumente nach docs/, Entscheidungen festgehalten
CLAUDE.md und README.md verweisen auf docs/, die Dateien lagen aber im Wurzelverzeichnis. Verschoben. docs/13-entscheidungen.md hält fest, wo die Dokumente sich widersprechen und wie es aufgelöst wurde. Der wichtigste Punkt: 04-datenmodell.md verlangt eine Regel, die jedes UPDATE auf CaseEntryVersion verwirft, 05-akten-und-vorlagen.md verlangt, dass eine Umklassifizierung rückwirkend auf alle Versionen wirkt. Beides zusammen geht nicht. Aufgelöst über einen Trigger, der die Tabelle anhängend hält, aber das Anheben von minClearance zulässt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Akten, Vorlagen und Versionierung
|
||||
|
||||
## Die vier Ebenen einer Akte
|
||||
|
||||
| Ebene | Änderbar | Versioniert | Zweck |
|
||||
|---|---|---|---|
|
||||
| Kopfdaten | ja | nein, aber im Audit-Log | Status, Klassifizierung, Frist, Beteiligte |
|
||||
| Abschnitte | ja | ja, pro Feld | Aktueller Sachstand |
|
||||
| Verlauf | ja, mit Version und Grabstein | ja, pro Eintrag | Wie es dazu kam |
|
||||
| Chat | Bearbeiten mit Vermerk | nein | Informelle Absprache, kein Aktenbestandteil |
|
||||
|
||||
Die Trennung zwischen Abschnitt und Verlauf ist nicht verhandelbar. Ein Abschnitt beantwortet
|
||||
„was wissen wir jetzt", der Verlauf „wie sind wir dahin gekommen".
|
||||
|
||||
Der Verlauf ist nachträglich änderbar, aber jede Änderung ist sichtbar und nachvollziehbar.
|
||||
Siehe „Nachträgliche Änderungen" weiter unten.
|
||||
|
||||
## Vorlagen
|
||||
|
||||
```ts
|
||||
type TemplateField = {
|
||||
key: string // stabil, nie ändern
|
||||
label: string
|
||||
type: 'longtext' | 'shorttext' | 'select' | 'multiselect'
|
||||
| 'date' | 'person' | 'number' | 'checklist' | 'table'
|
||||
options?: string[] // für select/multiselect
|
||||
required?: boolean
|
||||
requiredToClose?: boolean // blockiert den Abschluss des Falls
|
||||
minClearance?: number // Feld erst ab dieser Stufe sichtbar
|
||||
help?: string
|
||||
}
|
||||
```
|
||||
|
||||
Vorlagen sind versioniert. Eine Akte merkt sich `templateId` und `templateKey`. Änderungen an
|
||||
einer Vorlage erzeugen eine neue Version und lassen bestehende Akten unberührt. Im
|
||||
Vorlagen-Editor gibt es dafür eine Vorschau „So sieht eine neue Akte damit aus".
|
||||
|
||||
Startvorlagen im Seed: Korruptionsverdacht, Dienstvergehen, Sicherheitsvorfall, Routineprüfung.
|
||||
|
||||
## Feldebene-Klassifizierung
|
||||
|
||||
`minClearance` pro Feld ist das, was das Stufensystem lebendig macht. Für Nutzer unterhalb der
|
||||
Stufe wird der Wert serverseitig durch
|
||||
|
||||
```ts
|
||||
{ redacted: true, requiredLevel: 4 }
|
||||
```
|
||||
|
||||
ersetzt. Die Oberfläche zeigt einen Schwärzungsbalken mit „Geschwärzt, ab Stufe 4 sichtbar".
|
||||
Der echte Wert verlässt den Server nicht.
|
||||
|
||||
## Schwärzung im Freitext
|
||||
|
||||
Im Editor markiert man einen Bereich und wählt eine Stufe. Gespeichert wird als Auszeichnung:
|
||||
|
||||
```
|
||||
Der Hinweis kam von [[c4]]Wachmann Perreira, Sektor B[[/c4]] am 27.08.
|
||||
```
|
||||
|
||||
Der Server ersetzt den Inhalt beim Rendern für niedrigere Stufen durch einen Balken gleicher
|
||||
Länge (nicht gleicher Text, sonst lässt sich die Länge auswerten: immer auf ein festes Raster
|
||||
runden). Der Parser gehört in `lib/redaction.ts` und wird sowohl für die Ansicht als auch für
|
||||
den PDF-Export benutzt.
|
||||
|
||||
## Versionierung der Abschnitte
|
||||
|
||||
Jede Änderung schreibt `CaseSectionVersion` und erhöht `currentVersion`. Die UI zeigt
|
||||
`v4 · Vogel` neben der Überschrift, Klick öffnet einen Diff (Zeilen- oder Wortdiff, `diff`-Paket).
|
||||
|
||||
Beim Speichern schickt der Client die Version mit, die er geladen hat. Weicht sie ab, kommt
|
||||
409 zurück und die UI zeigt beide Fassungen nebeneinander mit „Meine übernehmen" oder
|
||||
„Fremde übernehmen". Kein stilles Überschreiben.
|
||||
|
||||
## Verlauf
|
||||
|
||||
Statusänderungen und Klassifizierungsänderungen erzeugen automatisch einen `SYSTEM`-Eintrag.
|
||||
`SYSTEM`-Einträge sind von jeder Änderung ausgenommen.
|
||||
|
||||
Neben der nachträglichen Änderung bleibt der Korrektureintrag bestehen: ein neuer Eintrag mit
|
||||
`type = CORRECTION` und `correctsId`. Das ist der richtige Weg, wenn sich der **Sachverhalt**
|
||||
geändert hat. Nachträgliches Ändern ist der richtige Weg, wenn sich die **Darstellung**
|
||||
geändert hat, also Tippfehler, vergessener Zusatz, falsche Uhrzeit im eigenen Vermerk.
|
||||
|
||||
## Nachträgliche Änderungen
|
||||
|
||||
Verlaufseinträge dürfen geändert und zurückgezogen werden. Ein hartes Löschen gibt es nicht,
|
||||
weder über die Oberfläche noch über eine Server Action.
|
||||
|
||||
### Änderungsrichtlinie
|
||||
|
||||
Global einstellbar im Adminbereich, `AppSetting.entryEditPolicy`:
|
||||
|
||||
| Wert | Bedeutung |
|
||||
|---|---|
|
||||
| `APPEND_ONLY` | Keine Änderung möglich, nur Korrektureinträge |
|
||||
| `GRACE_WINDOW` | Eigene Einträge innerhalb von N Minuten frei änderbar, danach nur mit Recht **(Auslieferungszustand, N = 15)** |
|
||||
| `OPEN` | Jederzeit änderbar, sofern das Recht vorliegt |
|
||||
|
||||
Zusätzlich: `entryEditGraceMinutes` (Standard 15) und `entryEditMarkGrace` (Standard `false`).
|
||||
Steht letzteres auf `false`, bekommt eine Änderung innerhalb der Karenzzeit **keine sichtbare
|
||||
Markierung**, wird aber trotzdem versioniert und protokolliert. Ein Tippfehler, der zwei
|
||||
Minuten nach dem Absenden korrigiert wird, soll die Akte nicht mit „bearbeitet"-Marken
|
||||
zupflastern. Alles danach ist sichtbar markiert.
|
||||
|
||||
### Was geändert werden darf
|
||||
|
||||
| Feld | Änderbar |
|
||||
|---|---|
|
||||
| `body` | ja |
|
||||
| `minClearance` | ja, mit Recht `case.edit` |
|
||||
| `type` | nein |
|
||||
| `authorId` | **nie** |
|
||||
| `createdAt` | **nie** |
|
||||
| `caseId` | nein, ein Eintrag wandert nicht in eine andere Akte |
|
||||
|
||||
Autor und Entstehungszeit sind unantastbar. Wer das umgehen will, muss den Eintrag
|
||||
zurückziehen und einen neuen schreiben, und genau das ist dann auch sichtbar.
|
||||
|
||||
### Versionierung
|
||||
|
||||
Jede Änderung schreibt den **vorherigen** Stand nach `CaseEntryVersion` und erhöht
|
||||
`CaseEntry.version`. Die Versionstabelle ist strikt anhängend und wird auch von Admins nicht
|
||||
gelöscht.
|
||||
|
||||
Die UI zeigt am Eintrag `bearbeitet · v3`. Ein Klick öffnet die Historie mit Wortdiff,
|
||||
Zeitpunkt und bearbeitender Person. **Diese Historie sieht jeder, der den Eintrag sehen darf**,
|
||||
nicht nur die Leitung. Das ist der ganze Sinn der Sache: In einer Abteilung, die Korruption
|
||||
aufdeckt, muss nachprüfbar sein, dass niemand still etwas umgeschrieben hat.
|
||||
|
||||
Ab der zweiten Änderung oder bei Änderung eines fremden Eintrags ist eine **Begründung
|
||||
Pflicht** (`changeReason`, mindestens 10 Zeichen). Sie steht in der Historie.
|
||||
|
||||
### Zurückziehen statt Löschen
|
||||
|
||||
`retractedAt`, `retractedById` und `retractionReason` (Pflicht) werden gesetzt. Der Eintrag
|
||||
bleibt an seiner Stelle im Verlauf stehen und zeigt:
|
||||
|
||||
> Eintrag zurückgezogen am 31.08.2026 durch Ltr. Reinhardt · Begründung: Falscher Fall
|
||||
|
||||
Der ursprüngliche Text bleibt in der Datenbank und ist für Nutzer mit `audit.read` weiterhin
|
||||
lesbar, für alle anderen nicht. Zurückziehen lässt sich rückgängig machen (`entry.retract`),
|
||||
was wiederum protokolliert wird.
|
||||
|
||||
### Umklassifizierung wirkt rückwirkend
|
||||
|
||||
Wird `minClearance` eines Eintrags erhöht, **erben alle bestehenden Versionen den neuen Wert**.
|
||||
Sonst liest jemand mit niedrigerer Stufe den ursprünglichen Text einfach in der Historie.
|
||||
Das ist die Stelle, an der eine naive Umsetzung leckt.
|
||||
|
||||
### Gesperrte Zustände
|
||||
|
||||
- `CLOSED`: Änderungen nur mit `case.edit_closed`, erzeugen zusätzlich einen `SYSTEM`-Eintrag
|
||||
„Nachträgliche Änderung nach Abschluss"
|
||||
- `ARCHIVED` und `SEALED`: keine Änderungen, auch nicht für Admins
|
||||
|
||||
### Protokollierung
|
||||
|
||||
Jede Änderung schreibt einen `AuditLog`-Eintrag mit Aktion `entry.edit`, `entry.retract` oder
|
||||
`entry.restore`, der Eintrags-ID, der neuen Versionsnummer, der Begründung und einem
|
||||
kompakten Diff in `meta`. Der Audit-Log wird nie gelöscht und ist die zweite, unabhängige
|
||||
Spur neben der Versionstabelle.
|
||||
|
||||
### Änderungsübersicht je Akte
|
||||
|
||||
Eigener Reiter an der Akte: alle nachträglichen Änderungen an Abschnitten und Verlauf,
|
||||
chronologisch, mit Person, Zeitpunkt, Begründung und Diff. Für den Leiter ist das die Ansicht,
|
||||
mit der er im Zweifel prüft, ob an einer Akte nachträglich gedreht wurde. Sichtbar für alle,
|
||||
die die Akte lesen dürfen.
|
||||
|
||||
### Folgen für andere Teile
|
||||
|
||||
- **Export**: `updatedAt` ändert sich, der PDF-Cache verfällt automatisch. Auf dem Deckblatt
|
||||
steht ein `Stand:`-Zeitstempel, damit eine ältere ausgedruckte Fassung erkennbar ist.
|
||||
- **Suche**: Der Trigger auf `search_vector` muss auch bei `UPDATE` auf `CaseEntry` feuern.
|
||||
- **Ungelesen**: Eine Änderung setzt die Akte für andere wieder auf ungelesen und löst
|
||||
dieselbe Benachrichtigung aus wie ein neuer Eintrag, mit dem Zusatz „geändert".
|
||||
- **Echtzeit**: Ereignis `entry.updated` über SSE, Clients tauschen den Eintrag anhand der ID.
|
||||
|
||||
## Lebenszyklus
|
||||
|
||||
```
|
||||
DRAFT ──► OPEN ──► REVIEW ──► CLOSED ──► ARCHIVED
|
||||
│ ▲ │
|
||||
▼ │ └──► OPEN (Rückgabe zur Nachbesserung)
|
||||
DORMANT
|
||||
|
||||
SEALED: aus jedem Zustand erreichbar, nur mit Recht 'admin' oder Stufe 5 lesbar
|
||||
```
|
||||
|
||||
Regeln:
|
||||
- `OPEN → REVIEW` braucht `case.edit`
|
||||
- `REVIEW → CLOSED` braucht `case.close` und alle `requiredToClose`-Felder gefüllt
|
||||
- `CLOSED → OPEN` braucht `case.reopen` und erzeugt einen Pflicht-Vermerk mit Begründung
|
||||
- `ARCHIVED` ist schreibgeschützt, nur noch lesbar
|
||||
|
||||
## Rückverweise
|
||||
|
||||
Auf einer Personalakte stehen automatisch die Fälle, in denen die Person vorkommt, getrennt
|
||||
nach Rolle. **Diese Liste läuft durch dieselbe Sichtbarkeitsprüfung wie die Fallliste.** Ein
|
||||
Nutzer, der einen Fall nicht sehen darf, sieht ihn hier auch nicht und die Zählung enthält ihn
|
||||
nicht. Das ist der häufigste Ort, an dem ein Aktensystem Informationen leaken lässt.
|
||||
Reference in New Issue
Block a user