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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f5769ca58a
commit
d65cc95eb2
+75
-4
@@ -211,8 +211,11 @@ enum LinkKind { RELATED SUCCESSOR MERGED_INTO }
|
||||
|
||||
model PersonnelFile {
|
||||
id String @id @default(uuid())
|
||||
userId String? // null bei NPCs
|
||||
userId String? // null bei Personen ohne Discord-Konto
|
||||
displayName String
|
||||
note String? // Freitext im Kopf der Akte
|
||||
createdById String?
|
||||
documents Document[]
|
||||
serviceNumber String? @unique
|
||||
rank String?
|
||||
department String?
|
||||
@@ -223,6 +226,66 @@ model PersonnelFile {
|
||||
@@index([displayName])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dokumente (Phase 4)
|
||||
//
|
||||
// Eine Ausfertigung aus einer Vorlage: Vorfallsbericht, Sonderarbeitsvertrag,
|
||||
// Bussgeldbescheid. Besitzer ist eine Fallakte, eine Personalakte oder nichts;
|
||||
// hoechstens einer der beiden Fremdschluessel ist gesetzt, gesichert durch eine
|
||||
// CHECK-Bedingung in der Migration.
|
||||
//
|
||||
// Anhaengend wie ein Verlaufseintrag: es gibt kein DELETE, eine Zuruecknahme
|
||||
// laesst einen Grabstein stehen. Siehe docs/13-entscheidungen.md.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model Document {
|
||||
id String @id @default(uuid(7))
|
||||
documentNumber String @unique // SAV-2026-0007
|
||||
title String
|
||||
templateId String
|
||||
templateKey String
|
||||
templateVersion Int
|
||||
layout DocumentLayout @default(FORM) // Kopie aus der Vorlage
|
||||
status DocumentStatus @default(DRAFT)
|
||||
classification Int @default(1)
|
||||
caseId String?
|
||||
personnelFileId String?
|
||||
createdById String
|
||||
finalizedAt DateTime?
|
||||
retractedAt DateTime?
|
||||
retractionReason String?
|
||||
searchVector Unsupported("tsvector")?
|
||||
sections DocumentSection[]
|
||||
@@index([caseId, createdAt(sort: Desc)])
|
||||
@@index([personnelFileId, createdAt(sort: Desc)])
|
||||
}
|
||||
|
||||
enum DocumentStatus { DRAFT FINAL RETRACTED }
|
||||
enum DocumentLayout { FORM PROSE NOTE }
|
||||
|
||||
// Wie CaseSection, eine Ebene weiter: versioniert, minClearance je Feld,
|
||||
// Diff, Protokoll. Die Regeln stehen einmal in lib/sections/render.ts.
|
||||
model DocumentSection {
|
||||
id String @id @default(uuid(7))
|
||||
documentId String
|
||||
fieldKey String
|
||||
minClearance Int @default(0)
|
||||
currentVersion Int @default(1)
|
||||
value Json
|
||||
updatedById String
|
||||
versions DocumentSectionVersion[]
|
||||
@@unique([documentId, fieldKey])
|
||||
}
|
||||
|
||||
model DocumentSectionVersion {
|
||||
id String @id @default(uuid(7))
|
||||
sectionId String
|
||||
version Int
|
||||
value Json
|
||||
authorId String
|
||||
@@unique([sectionId, version])
|
||||
}
|
||||
|
||||
model Attachment {
|
||||
id String @id @default(uuid())
|
||||
entryId String?
|
||||
@@ -316,6 +379,14 @@ CREATE INDEX case_open_idx ON "Case" (updated_at DESC) WHERE status IN ('OPEN','
|
||||
CREATE SEQUENCE case_number_2026 START 1;
|
||||
```
|
||||
|
||||
Die Nummernvergabe läuft über eine Tabelle `CaseCounter(year, lastNumber)` mit
|
||||
`SELECT ... FOR UPDATE` in derselben Transaktion wie das Anlegen, damit bei parallelem
|
||||
Anlegen keine Lücken oder Doppelungen entstehen.
|
||||
Die Nummernvergabe läuft über eine Tabelle mit `INSERT ... ON CONFLICT DO UPDATE` in
|
||||
derselben Transaktion wie das Anlegen, damit bei parallelem Anlegen keine Lücken oder
|
||||
Doppelungen entstehen.
|
||||
|
||||
**Abweichungen gegenüber dem Entwurf oben**, beide in Phase 4 entstanden und in
|
||||
`docs/13-entscheidungen.md` begründet:
|
||||
|
||||
| Entwurf | Gebaut |
|
||||
|---|---|
|
||||
| `CaseTemplate` | `DocumentTemplate`, plus `uses` und `layout` |
|
||||
| `CaseCounter(year, lastNumber)` | `NumberSeries(prefix, year, lastNumber)` — ein Nummernkreis je Präfix, für Aktenzeichen und Dokumentnummern |
|
||||
|
||||
Reference in New Issue
Block a user