Protokoll im Adminbereich, ein Knopf für das ganze Blatt

Die restlichen zwei Änderungswünsche.

**Protokoll.** „Wer was wo macht" stand seit dem ersten Tag in AuditLog —
harte Regel 3 sorgt dafür. Was fehlte, war die Ansicht darauf. /admin/protokoll
ist genau das und keine zweite Aufzeichnung: filterbar nach Person, Aktion,
Art und Zeitraum, rückwärts blätternd.

Nur für die Leitung. Eine Zeile nennt Aktenzeichen und Namen, und schon die
Existenz einer Zeile verrät, dass es einen Vorgang gibt; ein gefiltertes
Protokoll für alle wäre ein Weg, die Sichtbarkeitsregeln von hinten
aufzurollen. Aus demselben Grund werden Kennungen ohne Sichtbarkeitsfilter zu
Aktenzeichen aufgelöst — ein Protokoll aus UUIDs liest niemand, und eines, das
niemand liest, ist keine Kontrolle.

**Speichern.** Jedes Feld trug seinen eigenen Knopf: ein Arbeitsvertrag mit
zwölf Feldern hiess zwölfmal speichern. Der Knopf steht jetzt am Blatt.

Geschrieben wird trotzdem je Feld und nicht als Transaktion, und das folgt aus
dem Datenmodell: Abschnitte sind einzeln versioniert, weil mehrere Leute
gleichzeitig an einer Akte arbeiten. Als Transaktion blockierte ein einziges
zwischenzeitlich geändertes Feld das ganze Blatt — elf gute Änderungen verloren,
weil jemand das zwölfte angefasst hat. So geht durch, was durchgeht, und die
Rückmeldung sagt, was offen blieb und wo. Lichtbilder bleiben ausgenommen: ein
Upload mit Prüfkette ist keine Feldänderung.

lib/audit.ts liegt jetzt als lib/audit/write.ts neben lib/audit/query.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Harlekin
2026-09-03 15:16:10 +02:00
co-authored by Claude Opus 5
parent ef377f0123
commit 5f9f5d99fb
40 changed files with 1005 additions and 42 deletions
+75
View File
@@ -1,3 +1,78 @@
export const auditText = {
heading: 'Protokoll',
intro:
'Wer was wo getan hat. Jede schreibende Aktion steht hier, dazu jeder Lesezugriff ab Stufe 3. Einträge lassen sich nicht ändern und nicht entfernen.',
when: 'Zeitpunkt',
who: 'Wer',
what: 'Was',
where: 'Wo',
clearance: 'Stufe',
filterActor: 'Person',
filterAction: 'Aktion',
filterTarget: 'Art',
filterFrom: 'Ab',
filterTo: 'Bis',
apply: 'Filtern',
reset: 'Alles zeigen',
all: 'Alle',
empty: 'Für diesen Ausschnitt steht nichts im Protokoll.',
older: 'Ältere anzeigen',
gone: 'nicht mehr auffindbar',
ofUser: 'Nur von dieser Person',
ofTarget: 'Nur zu diesem Vorgang',
actions: {
'case.read': 'Fallakte gelesen',
'case.create': 'Fallakte angelegt',
'case.update': 'Fallakte geändert',
'case.reclassify': 'Fallakte umklassifiziert',
'case.status': 'Status geändert',
'entry.create': 'Vermerk angelegt',
'entry.edit': 'Vermerk geändert',
'entry.retract': 'Vermerk zurückgezogen',
'entry.restore': 'Vermerk wiederhergestellt',
'case.participant.add': 'Beteiligten eingetragen',
'case.participant.remove': 'Beteiligten ausgetragen',
'case.link': 'Akten verknüpft',
'case.unlink': 'Verknüpfung gelöst',
'personnel.read': 'Personalakte gelesen',
'personnel.create': 'Personalakte angelegt',
'personnel.update': 'Personalakte geändert',
'personnel.reclassify': 'Personalakte umklassifiziert',
'personnel.assign': 'Person zugeordnet',
'personnel.unassign': 'Zuordnung aufgehoben',
'document.read': 'Dokument gelesen',
'document.create': 'Dokument angelegt',
'document.update': 'Dokument geändert',
'document.finalize': 'Dokument abgeschlossen',
'document.retract': 'Dokument zurückgezogen',
'document.reclassify': 'Dokument umklassifiziert',
'attachment.upload': 'Datei hochgeladen',
'attachment.read': 'Datei abgerufen',
'attachment.release': 'Datei freigegeben',
'chat.adopt': 'Nachricht als Vermerk übernommen',
'roles.update': 'Rollenzuordnung geändert',
'export.pdf': 'PDF erzeugt',
'export.approve': 'Export freigegeben',
'export.reject': 'Export abgelehnt',
'export.download': 'Export heruntergeladen',
'export.database': 'Datenbankabzug erzeugt',
} as Record<string, string>,
targets: {
Case: 'Fallakte',
CaseEntry: 'Vermerk',
CaseSection: 'Abschnitt',
PersonnelFile: 'Personalakte',
Document: 'Dokument',
DocumentSection: 'Dokumentabschnitt',
Attachment: 'Datei',
ChatMessage: 'Nachricht',
ExportJob: 'Export',
RoleMapping: 'Rollenzuordnung',
ChangePolicy: 'Änderungsrichtlinie',
Database: 'Datenbank',
} as Record<string, string>,
} as const
export const adminText = {
heading: 'Admin',
intro: 'Rollen, Vorlagen, Regeln und das Protokoll der Abteilung.',
+7
View File
@@ -64,9 +64,16 @@ export const sectionText = {
heading: 'Abschnitte',
empty: 'Die Vorlage dieser Fallart hat keine Abschnitte.',
save: 'Abschnitt speichern',
saveAll: 'Abschnitte speichern',
saving: 'Wird gespeichert',
saved: 'Gespeichert',
draftKept: 'Entwurf lokal gesichert',
unchanged: 'Nichts geändert',
changedCount: (n: number) => (n === 1 ? '1 Feld geändert' : `${n} Felder geändert`),
savedCount: (n: number) => (n === 1 ? '1 Feld gespeichert' : `${n} Felder gespeichert`),
partly: (gespeichert: number, offen: number) =>
`${gespeichert} gespeichert, ${offen} offen. Die offenen stehen unten mit beiden Fassungen.`,
discard: 'Änderungen verwerfen',
version: (v: number) => `v${v}`,
history: 'Fassungen',
conflict:
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Aktenchat und freie Kanäle.
* Der Chat einer Akte.
*
* Eigene Datei und nicht bei den Fallakten, obwohl der Aktenchat an einer
* Akte hängt: die Fallakte lädt ihre Texte sofort, den Chat erst beim
+2
View File
@@ -43,6 +43,7 @@ import { documentText, attachmentText } from '@/lib/i18n/documents'
import { exportText } from '@/lib/i18n/export'
import {
adminText,
auditText,
roleAdminText,
templateAdminText,
settingsText,
@@ -55,6 +56,7 @@ export const de = {
auth: authText,
overview: overviewText,
admin: adminText,
audit: auditText,
roleAdmin: roleAdminText,
clearance: clearanceText,
case: caseText,
+1
View File
@@ -11,6 +11,7 @@
*/
export const documentText = {
saveAll: 'Dokument speichern',
one: 'Dokument',
many: 'Dokumente',
number: 'Dokumentnummer',
+2
View File
@@ -179,6 +179,8 @@ export const paletteText = {
{ href: '/faelle/neu', label: 'Neue Fallakte' },
{ href: '/personalakten', label: 'Personalakten' },
{ href: '/suche', label: 'Suche' },
{ href: '/admin', label: 'Admin' },
{ href: '/admin/protokoll', label: 'Protokoll' },
{ href: '/einstellungen', label: 'Benachrichtigungen' },
],
} as const