Files
AfiS-Aktensystem/docs/05-akten-und-vorlagen.md
T
HarlekinandClaude Opus 5 d65cc95eb2 Phase 4 abgeschlossen: Personalakten, Dokumente, Beteiligte, Verknüpfungen
Die Dokumentebene aus docs/13 ist gebaut, additiv: Document, DocumentSection
und DocumentSectionVersion neben Case, CaseSection und CaseEntry. Ein Dokument
hängt an einer Fallakte, an einer Personalakte oder an nichts, höchstens an
einem — gesichert durch eine CHECK-Bedingung. Es gibt kein DELETE; eine
Zurücknahme lässt einen Grabstein stehen, der auch für den Admin zu ist.

Eine neue Dokumentart ist damit ein Datensatz, kein Deployment. Im Seed stehen
vier: vermerk, vorfallsbericht, sonderarbeitsvertrag (mehrseitiger Vertragstext
mit Platzhaltern und Unterschriftenzeilen) und bussgeld.

Zwei Umbenennungen, beide von Hand migriert, damit die vorhandenen Zeilen und
Zählerstände stehen bleiben:
- CaseTemplate -> DocumentTemplate, plus uses[] und layout
- CaseCounter -> NumberSeries, ein Nummernkreis je Präfix für Aktenzeichen
  und Dokumentnummern

Die Regeln für Abschnitte stehen jetzt einmal in lib/sections/render.ts und
gelten für Fallakten und Dokumente gleichermaßen. Die Frage aus docs/13, ob
CaseSection langfristig in die Dokumentebene wandert, ist damit verneint und
begründet.

Rückverweise auf der Personalakte laufen durch visibleCaseWhere, die Zahl
daneben entsteht aus dem gefilterten Ergebnis. Auch die Rollengruppe
verschwindet mit, sonst verriete "Betroffener" ohne Eintrag die Ermittlung.
Die eigene Personalakte ist keine Ausnahme von der Freigabestufe.

Vier neue Rechte: document.create, .edit, .finalize, .retract. Das letzte
steht bei den zurückgehaltenen, es greift in Geschriebenes ein.

91 Tests im Web-Paket, 15 in der Datenbank, 28 e2e. Größte Route 118 kB
gegen das Budget von 120 kB — die Luft wird knapp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 14:15:16 +02:00

10 KiB

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

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

{ 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, 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.

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.