Das Formular der Personalabteilung ist übernommen, nicht durch eine Bildschirmansicht ersetzt: schwarzer Rahmen, Kopf mit Kennung und Klassifizierungsbalken, Raster aus fünf Spalten, Unterschriftenzeilen, Fußzeile. Es steht bewusst außerhalb des Design-Systems — es wird gedruckt und muss auf Papier aussehen wie das Blatt daneben. Datengetrieben bleibt es trotzdem. Kein Feld steht im Programm: sheetTitle und sheetSubtitle an der Vorlage, span für die Zellbreite, rows für das Lichtbild, dateKey für das Datum unter einer Unterschrift, slot für den Klassifizierungsbalken. Eine weitere Dokumentart mit layout = DOSSIER braucht keine Zeile Code. Der Balken zeigt die Foundation-Stufe aus der neuen Tabelle FoundationClearance. Sie ist Inhalt, kein Zugriff — was jemand sehen darf, entscheidet weiterhin allein die interne Stufe. Ohne Auswahl bleibt der Balken neutral, statt eine Einstufung zu behaupten. Die Dienstnummer ist entfallen, es gibt sie nicht. Der Status ist zu mehreren Etiketten geworden: "Gekündigt" und "Verhaftet" schließen einander nicht aus. Unbekannte Schlüssel werden verworfen und die Reihenfolge kommt aus der Konfiguration, nicht aus der Eingabe. PersonnelAssignment hängt AfiS-Mitglieder an eine Person — Aktenführung, Ermittler, Ansprechpartner — und ist zugleich der ausnahmsweise Zugang, wie CaseParticipant bei der Fallakte. Betroffen zu sein gibt weiterhin keinen Zugang: das ist eine Eigenschaft der Person, die Zuordnung eine Entscheidung der Abteilung. Fall und Person lassen sich von beiden Seiten verknüpfen und schon beim Anlegen einer Fallakte. Alle drei Wege laufen in addParticipant zusammen. Noch offen: das Lichtbild. Feldtyp und Platz stehen, hochladen kommt mit der Prüfkette in Phase 5 — ein Bild als Data-URL im Abschnittswert verstiesse gegen harte Regel 4. 102 Tests im Web-Paket, davon sechs auf dem gerenderten Blatt selbst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
234 lines
10 KiB
Markdown
234 lines
10 KiB
Markdown
# 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.
|
|
|
|
Auch die **Rollengruppe** verschwindet mit. Stünde „Betroffener" als Überschrift ohne Eintrag
|
|
darunter, wäre die Ermittlung gegen die Person verraten, ohne dass ein Aktenzeichen sichtbar
|
|
wird. Der Negativtest dazu steht in `web/tests/personalakte.test.ts`.
|
|
|
|
## Was in einer Akte steht
|
|
|
|
Jede Akte ist ein Behälter, der Inhalt sind Beiträge. Eine Fallakte hat vier Ebenen:
|
|
|
|
| Ebene | Was | Modell |
|
|
|---|---|---|
|
|
| Kopfdaten | Aktenzeichen, Titel, Status, Freigabestufe, Wiedervorlage | `Case` |
|
|
| Abschnitte | aktueller Sachstand aus der Vorlage der Fallart | `CaseSection` |
|
|
| Verlauf | Kurzvermerke, chronologisch | `CaseEntry` |
|
|
| Dokumente | aus einer Vorlage erzeugt, versioniert, einzeln exportierbar | `Document` |
|
|
|
|
Eine Personalakte hat Kopfdaten, Zuständige, Dokumente und Rückverweise, aber keine Abschnitte
|
|
und keinen eigenen Verlauf: was über eine Person aktenkundig ist, steht in Dokumenten an ihr
|
|
oder in den Fällen, in denen sie vorkommt. Ein kurzer Vermerk an einer Person ist ein Dokument
|
|
aus der Vorlage `vermerk` — dieselbe Versionierung, dieselbe Feldstufe, dasselbe Protokoll wie
|
|
bei allem anderen. Siehe `docs/13-entscheidungen.md`.
|
|
|
|
Das erste Dokument entsteht mit der Akte: die Vorlage `personalakte` ist das **Aktenblatt** der
|
|
Abteilung, übernommen aus dem Formular, das sie bislang von Hand ausgefüllt hat. Es hat
|
|
`layout = DOSSIER` und wird gedruckt, nicht nur gelesen. Was darauf steht und in welcher Zelle,
|
|
bestimmt allein die Vorlage.
|
|
|
|
Ein Dokument hat drei Zustände: `DRAFT` in Arbeit, `FINAL` ausgefertigt, `RETRACTED`
|
|
zurückgezogen. Es gibt kein Löschen. Ein zurückgezogenes Dokument bleibt als Grabstein
|
|
stehen, mit Datum, Person und Begründung, und ist auch für den Administrator
|
|
schreibgeschützt — sonst liesse sich ein zurückgezogenes Dokument nachträglich zu etwas
|
|
anderem machen.
|