feat(datenmodell): Schema, tagCodes und Geheimnisse (#20)
Vollständiges Datenmodell in deutscher Benennung: 20 Tabellen, 9 Aufzählungen, eine Migration, dazu die framework-freien Helfer für tagCodes und Geheimnisse. Regeln stehen zusätzlich in der Datenbank, nicht nur im Service: 12 CHECKs, 2 partielle Eindeutigkeitsindizes, 4 zusammengesetzte Fremdschlüssel. Die Mandantengrenze ist damit strukturell erzwungen. Schrittergebnisse, Kommentare und Protokolleinträge kaskadieren bewusst nicht — das Archiv ist das Gedächtnis der Anwendung. 106 Tests, davon 42 gegen die echten Datenbankregeln. Closes #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #20.
This commit is contained in:
+579
-9
@@ -1,7 +1,19 @@
|
||||
// Laufzettel – Datenmodell
|
||||
// Benennung deutsch, Umlaute ASCII-gefaltet (siehe CLAUDE.md, Abschnitt Namensabbildung).
|
||||
// Das vollstaendige Schema entsteht in #006; hier steht nur so viel, wie der
|
||||
// Generator zum Laufen braucht.
|
||||
//
|
||||
// Benennung deutsch, Umlaute ASCII-gefaltet. Die Abbildung auf die englischen
|
||||
// Modellnamen der Auftragsvorgabe steht in CLAUDE.md.
|
||||
//
|
||||
// Zwei Entwurfsentscheidungen tragen das Ganze, siehe docs/architektur.md:
|
||||
//
|
||||
// 1. Jede kundenbezogene Zeile traegt IMMER eine kundeId – auch im Modus
|
||||
// INTERN, wo es die eine Organisation mit istEigeneOrganisation = true ist.
|
||||
// Dadurch ist der Wechsel in den Modus DIENSTLEISTUNG eine Datenaenderung
|
||||
// und keine Migration.
|
||||
//
|
||||
// 2. Nichts wird geloescht, woran ein Lauf haengt. Geraete, Abteilungen und
|
||||
// Vorlagen werden archiviert, Mitarbeiter nur auf aktiv = false gesetzt.
|
||||
// Deshalb stehen die Fremdschluessel auf Restrict; Cascade gibt es nur
|
||||
// innerhalb einer Vorlagen-Version, die als Ganzes verworfen werden darf.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
@@ -12,6 +24,8 @@ datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
// ---- Aufzaehlungen ---------------------------------------------------------
|
||||
|
||||
enum Rolle {
|
||||
ADMIN
|
||||
REDAKTEUR
|
||||
@@ -19,16 +33,572 @@ enum Rolle {
|
||||
LESER
|
||||
}
|
||||
|
||||
enum Betriebsmodus {
|
||||
INTERN
|
||||
DIENSTLEISTUNG
|
||||
}
|
||||
|
||||
enum LaufStatus {
|
||||
OFFEN
|
||||
IN_ARBEIT
|
||||
ABGESCHLOSSEN
|
||||
ARCHIVIERT
|
||||
}
|
||||
|
||||
enum SchrittZustand {
|
||||
OFFEN
|
||||
ERLEDIGT
|
||||
NICHT_ZUTREFFEND
|
||||
FEHLGESCHLAGEN
|
||||
}
|
||||
|
||||
// AKTION kommt in Phase 4 dazu. Weil der Typ ein Enum ist und die Einzelheiten
|
||||
// in VorlagenSchritt.konfig liegen, braucht das keine Migration der bestehenden
|
||||
// Schritte.
|
||||
enum SchrittTyp {
|
||||
HAKEN
|
||||
TEXT
|
||||
ZAHL
|
||||
AUSWAHL
|
||||
DATUM
|
||||
BEFEHL
|
||||
}
|
||||
|
||||
enum VariablenTyp {
|
||||
TEXT
|
||||
ZAHL
|
||||
AUSWAHL
|
||||
DATUM
|
||||
}
|
||||
|
||||
enum GeraeteTyp {
|
||||
PC
|
||||
NOTEBOOK
|
||||
SERVER
|
||||
FIREWALL
|
||||
SWITCH
|
||||
DRUCKER
|
||||
RAUM
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
enum VersionStatus {
|
||||
ENTWURF
|
||||
VEROEFFENTLICHT
|
||||
}
|
||||
|
||||
enum Herkunft {
|
||||
ABGLEICH
|
||||
MANUELL
|
||||
}
|
||||
|
||||
// ---- Einstellungen und Mandanten ------------------------------------------
|
||||
|
||||
/// Einzelne Zeile mit fester id "global". Der Betriebsmodus liegt bewusst in
|
||||
/// der Datenbank und nicht in der Umgebung, damit der Admin ohne Neustart
|
||||
/// umschalten kann.
|
||||
model Einstellung {
|
||||
id String @id @default("global")
|
||||
modus Betriebsmodus @default(INTERN)
|
||||
/// Globale Namensregeln fuer upn, sam, mail und hostname. Im Modus
|
||||
/// DIENSTLEISTUNG koennen Kunden eigene Regeln haben, die diese ueberlagern.
|
||||
namensregeln Json @default("{}")
|
||||
/// Ziel-OU fuer deaktivierte Konten, Vorbelegung im Offboarding.
|
||||
gesperrteOu String?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Kunde {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
/// 2 bis 6 Zeichen, erscheint auf Etiketten und in der Hostname-Regel.
|
||||
kuerzel String @unique
|
||||
/// Genau eine Zeile traegt true. Erzwungen durch einen partiellen eindeutigen
|
||||
/// Index in der Migration, den Prisma selbst nicht ausdruecken kann.
|
||||
istEigeneOrganisation Boolean @default(false)
|
||||
/// Namensregeln dieses Kunden; fehlende Schluessel fallen auf die globalen
|
||||
/// aus Einstellung zurueck.
|
||||
namensregeln Json @default("{}")
|
||||
notizen String?
|
||||
archiviert Boolean @default(false)
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
abteilungen Abteilung[]
|
||||
benutzerbindungen BenutzerKunde[]
|
||||
geraete Geraet[]
|
||||
laeufe Lauf[]
|
||||
mitarbeiter Mitarbeiter[]
|
||||
verzeichnisquellen Verzeichnisquelle[]
|
||||
vorlagen Vorlage[]
|
||||
|
||||
@@index([archiviert])
|
||||
}
|
||||
|
||||
// ---- Benutzer der Anwendung ------------------------------------------------
|
||||
|
||||
/// Die Menschen, die Laufzettel bedienen – nicht zu verwechseln mit Mitarbeiter,
|
||||
/// den Personen der Firma aus dem Verzeichnis.
|
||||
model Benutzer {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String
|
||||
rolle Rolle @default(LESER)
|
||||
/// Subject-Claim des OIDC-Anbieters. Leer bei rein lokalen Konten.
|
||||
oidcSub String? @unique
|
||||
/// Nur gesetzt, wenn der lokale Login benutzt wird. argon2, nie Klartext.
|
||||
passwortHash String?
|
||||
aktiv Boolean @default(true)
|
||||
/// Sitzungen, die vor diesem Zeitpunkt ausgestellt wurden, gelten nicht mehr.
|
||||
/// So laesst sich ein Konto sperren, ohne auf den Ablauf des Cookies zu warten.
|
||||
sitzungenUngueltigAb DateTime?
|
||||
letzteAnmeldungAm DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
abgeschlosseneLaeufe Lauf[] @relation("LaufAbgeschlossenVon")
|
||||
archivierteLaeufe Lauf[] @relation("LaufArchiviertVon")
|
||||
erstellteLaeufe Lauf[] @relation("LaufErstelltVon")
|
||||
zugewieseneLaeufe Lauf[] @relation("LaufZugewiesenAn")
|
||||
kommentare Kommentar[]
|
||||
kundenbindungen BenutzerKunde[]
|
||||
protokolle Protokoll[]
|
||||
schrittergebnisse Schrittergebnis[]
|
||||
veroeffentlichteVersionen VorlagenVersion[] @relation("VersionVeroeffentlichtVon")
|
||||
|
||||
@@index([aktiv])
|
||||
@@index([rolle])
|
||||
}
|
||||
|
||||
/// Bindet einen Benutzer an Kunden. Nur im Modus DIENSTLEISTUNG und nur, wenn
|
||||
/// OIDC_CUSTOMER_GROUP_PREFIX gesetzt ist: TECHNIKER und LESER sehen dann allein
|
||||
/// ihre Kunden, ADMIN und REDAKTEUR alles.
|
||||
///
|
||||
/// Die Bindung liegt in der Datenbank und nicht im Token, damit sie – wie Rolle
|
||||
/// und Aktiv-Status – pro Anfrage frisch gelesen wird und auch fuer lokale Konten
|
||||
/// gilt. Beim OIDC-Login wird sie neu geschrieben.
|
||||
model BenutzerKunde {
|
||||
benutzerId String
|
||||
kundeId String
|
||||
erstelltAm DateTime @default(now())
|
||||
|
||||
benutzer Benutzer @relation(fields: [benutzerId], references: [id], onDelete: Cascade)
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([benutzerId, kundeId])
|
||||
@@index([kundeId])
|
||||
}
|
||||
|
||||
// ---- Verzeichnis und Personen ---------------------------------------------
|
||||
|
||||
/// LDAP-Quelle fuer den Abgleich des Mitarbeiterverzeichnisses. In der Regel
|
||||
/// genau eine (das Firmen-AD); mehrere sind erlaubt, etwa eine zweite Domaene.
|
||||
/// Nur fuer die eigene Organisation – im Modus DIENSTLEISTUNG gibt es keinen
|
||||
/// Abgleich zu Kunden-ADs.
|
||||
model Verzeichnisquelle {
|
||||
id String @id @default(cuid())
|
||||
kundeId String
|
||||
name String
|
||||
aktiv Boolean @default(false)
|
||||
url String
|
||||
bindDn String
|
||||
/// AES-256-GCM aus APP_SECRET. Darf keine API-Antwort erreichen; dafuer sorgt
|
||||
/// der Serializer in der Service-Schicht, abgedeckt durch einen eigenen Test.
|
||||
bindPasswortVerschluesselt String
|
||||
basisDn String
|
||||
filter String
|
||||
/// Zuordnung der LDAP-Attribute auf unsere Felder, mit AD-Vorgaben.
|
||||
attributZuordnung Json
|
||||
/// Eigenes CA-Zertifikat, falls ldaps gegen eine interne CA laeuft.
|
||||
caZertifikatPem String?
|
||||
abgleichIntervallMinuten Int @default(60)
|
||||
letzterAbgleichAm DateTime?
|
||||
letzterAbgleichStatus String?
|
||||
/// Nimmt die Meldung der LDAP-Bibliothek auf. #016 muss sie filtern, bevor sie
|
||||
/// hier landet – rohe Bind-Fehler koennen das Passwort enthalten.
|
||||
letzterAbgleichFehler String?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
mitarbeiter Mitarbeiter[]
|
||||
|
||||
@@index([kundeId])
|
||||
@@index([aktiv])
|
||||
}
|
||||
|
||||
/// Die Personen der Firma bzw. des Kunden. Werden nie geloescht, nur auf
|
||||
/// aktiv = false gesetzt, damit archivierte Laeufe weiter auf sie zeigen.
|
||||
model Mitarbeiter {
|
||||
id String @id @default(cuid())
|
||||
kundeId String
|
||||
/// Nur bei Herkunft ABGLEICH gesetzt.
|
||||
quelleId String?
|
||||
/// objectGUID des Verzeichnisses, je Quelle eindeutig.
|
||||
externeId String?
|
||||
vorname String
|
||||
nachname String
|
||||
anzeigename String
|
||||
mail String?
|
||||
upn String?
|
||||
sam String?
|
||||
dn String?
|
||||
/// Der rohe department-Wert aus dem Verzeichnis. Die gepflegten Abteilungen
|
||||
/// stehen in Abteilung und werden ueber den Namen zugeordnet; ein Wert ohne
|
||||
/// passende Abteilung bleibt trotzdem erhalten.
|
||||
abteilung String?
|
||||
titel String?
|
||||
vorgesetzterExterneId String?
|
||||
aktiv Boolean @default(true)
|
||||
herkunft Herkunft @default(MANUELL)
|
||||
zuletztGesehenAm DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
quelle Verzeichnisquelle? @relation(fields: [quelleId], references: [id], onDelete: Restrict)
|
||||
geraete Geraet[]
|
||||
laeufe Lauf[]
|
||||
|
||||
/// Postgres behandelt NULL als verschieden, deshalb stoeren mehrere manuell
|
||||
/// angelegte Mitarbeiter ohne Quelle und externe Id hier nicht.
|
||||
@@unique([quelleId, externeId])
|
||||
/// Schluesselkandidat fuer den zusammengesetzten Fremdschluessel.
|
||||
@@unique([id, kundeId])
|
||||
@@index([kundeId, aktiv])
|
||||
@@index([abteilung])
|
||||
@@index([upn])
|
||||
@@index([sam])
|
||||
}
|
||||
|
||||
/// Vom Admin gepflegt, Vorschlaege entstehen aus den department-Werten des
|
||||
/// Abgleichs. Liefert beim Onboarding die Ziel-OU und die Standardgruppen.
|
||||
model Abteilung {
|
||||
id String @id @default(cuid())
|
||||
kundeId String
|
||||
name String
|
||||
zielOu String?
|
||||
standardGruppen String[] @default([])
|
||||
mailDomain String?
|
||||
notizen String?
|
||||
archiviert Boolean @default(false)
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
laeufe Lauf[]
|
||||
|
||||
@@unique([kundeId, name])
|
||||
/// Schluesselkandidat fuer den zusammengesetzten Fremdschluessel aus der
|
||||
/// Migration, der eine Abteilung eines fremden Kunden ausschliesst.
|
||||
@@unique([id, kundeId])
|
||||
@@index([kundeId, archiviert])
|
||||
}
|
||||
|
||||
// ---- Geraete und Tags ------------------------------------------------------
|
||||
|
||||
/// Nicht nur PCs: ein Serverschrank, ein Raum oder ein Auftrag bekommen
|
||||
/// genauso einen Aufkleber.
|
||||
///
|
||||
/// Ein Geraet ohne registriertAm ist ein vorgedruckter, noch nicht zugeordneter
|
||||
/// Tag. Erst beim ersten Scannen wird es zu einem echten Objekt – deshalb greift
|
||||
/// die Pflicht zu hostname oder bezeichnung erst ab dann (Check-Constraint in
|
||||
/// der Migration).
|
||||
model Geraet {
|
||||
id String @id @default(cuid())
|
||||
kundeId String
|
||||
/// Besitzer bzw. Hauptnutzer. Server, Raeume und Poolgeraete haben keinen.
|
||||
mitarbeiterId String?
|
||||
/// Wird nie geaendert und nie wiederverwendet, auch nicht nach Archivierung.
|
||||
/// Unabhaengig vom Hostname, damit ein Geraet umbenannt werden kann, ohne dass
|
||||
/// der Aufkleber ungueltig wird.
|
||||
tagCode String @unique
|
||||
hostname String?
|
||||
/// Anzeigename fuer Objekte ohne Hostname, etwa "Serverschrank Keller".
|
||||
bezeichnung String?
|
||||
seriennummer String?
|
||||
inventarnummer String?
|
||||
typ GeraeteTyp @default(SONSTIGES)
|
||||
modell String?
|
||||
notizen String?
|
||||
archiviert Boolean @default(false)
|
||||
registriertAm DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
mitarbeiter Mitarbeiter? @relation(fields: [mitarbeiterId], references: [id], onDelete: Restrict)
|
||||
laeufe Lauf[]
|
||||
|
||||
/// Schluesselkandidat fuer den zusammengesetzten Fremdschluessel.
|
||||
@@unique([id, kundeId])
|
||||
@@index([kundeId, archiviert])
|
||||
@@index([mitarbeiterId])
|
||||
@@index([typ])
|
||||
@@index([registriertAm])
|
||||
}
|
||||
|
||||
/// Jeder je vergebene tagCode, unabhaengig davon, ob das Geraet noch existiert.
|
||||
///
|
||||
/// Die Eindeutigkeit allein aus Geraet.tagCode zu ziehen reicht nicht: verschwindet
|
||||
/// die Zeile, waere der Code wieder frei und ein alter Aufkleber im Regal zeigte
|
||||
/// auf ein fremdes Objekt. Aus diesem Buch wird nie geloescht.
|
||||
model TagCodeVergabe {
|
||||
tagCode String @id
|
||||
vergebenAm DateTime @default(now())
|
||||
}
|
||||
|
||||
/// Vorlagen, die auf der Geraeteseite fuer diesen Geraetetyp als Schnellstart
|
||||
/// vorgeschlagen werden. Bewusst ohne kundeId: welcher Schnellstart zu einem
|
||||
/// Geraetetyp passt, haengt am Typ und nicht am Kunden. Kundenspezifische
|
||||
/// Vorlagen filtert die Geraeteseite ueber Vorlage.kundeId heraus.
|
||||
model GeraetetypVorlage {
|
||||
id String @id @default(cuid())
|
||||
geraeteTyp GeraeteTyp
|
||||
vorlageId String
|
||||
position Int @default(0)
|
||||
|
||||
vorlage Vorlage @relation(fields: [vorlageId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([geraeteTyp, vorlageId])
|
||||
@@index([geraeteTyp, position])
|
||||
}
|
||||
|
||||
// ---- Vorlagen --------------------------------------------------------------
|
||||
|
||||
model Vorlage {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String
|
||||
rolle Rolle @default(LESER)
|
||||
oidcSub String? @unique
|
||||
passwortHash String?
|
||||
aktiv Boolean @default(true)
|
||||
beschreibung String?
|
||||
kategorie String?
|
||||
schlagworte String[] @default([])
|
||||
/// Nur im Modus DIENSTLEISTUNG: kundenspezifische Vorlage. Leer heisst
|
||||
/// "gilt fuer alle".
|
||||
kundeId String?
|
||||
archiviert Boolean @default(false)
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
@@index([aktiv])
|
||||
kunde Kunde? @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
versionen VorlagenVersion[]
|
||||
typVorgaben GeraetetypVorlage[]
|
||||
|
||||
@@index([archiviert])
|
||||
@@index([kategorie])
|
||||
@@index([kundeId])
|
||||
}
|
||||
|
||||
/// Eine veroeffentlichte Version wird nie wieder veraendert. "Bearbeiten"
|
||||
/// erzeugt eine tiefe Kopie als neuen Entwurf; je Vorlage gibt es hoechstens
|
||||
/// einen Entwurf. Laeufe verweisen immer auf eine veroeffentlichte Version.
|
||||
model VorlagenVersion {
|
||||
id String @id @default(cuid())
|
||||
vorlageId String
|
||||
version Int
|
||||
status VersionStatus @default(ENTWURF)
|
||||
aenderungsnotiz String?
|
||||
veroeffentlichtAm DateTime?
|
||||
veroeffentlichtVonId String?
|
||||
erstelltAm DateTime @default(now())
|
||||
|
||||
vorlage Vorlage @relation(fields: [vorlageId], references: [id], onDelete: Cascade)
|
||||
veroeffentlichtVon Benutzer? @relation("VersionVeroeffentlichtVon", fields: [veroeffentlichtVonId], references: [id], onDelete: Restrict)
|
||||
abschnitte VorlagenAbschnitt[]
|
||||
variablen VorlagenVariable[]
|
||||
laeufe Lauf[]
|
||||
|
||||
@@unique([vorlageId, version])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
/// Wird beim Start eines Laufs abgefragt und fuellt die Platzhalter.
|
||||
model VorlagenVariable {
|
||||
id String @id @default(cuid())
|
||||
versionId String
|
||||
schluessel String
|
||||
beschriftung String
|
||||
typ VariablenTyp @default(TEXT)
|
||||
optionen String[] @default([])
|
||||
pflicht Boolean @default(false)
|
||||
position Int @default(0)
|
||||
|
||||
version VorlagenVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([versionId, schluessel])
|
||||
@@index([versionId, position])
|
||||
}
|
||||
|
||||
model VorlagenAbschnitt {
|
||||
id String @id @default(cuid())
|
||||
versionId String
|
||||
titel String
|
||||
position Int @default(0)
|
||||
|
||||
version VorlagenVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
|
||||
schritte VorlagenSchritt[]
|
||||
|
||||
@@index([versionId, position])
|
||||
}
|
||||
|
||||
model VorlagenSchritt {
|
||||
id String @id @default(cuid())
|
||||
abschnittId String
|
||||
position Int @default(0)
|
||||
titel String
|
||||
/// Markdown, in der Abarbeitungsansicht ausklappbar. Kein rohes HTML.
|
||||
hilfetext String?
|
||||
typ SchrittTyp @default(HAKEN)
|
||||
pflicht Boolean @default(false)
|
||||
/// Typabhaengige Einzelheiten: Auswahloptionen, Min/Max/Einheit, bei BEFEHL
|
||||
/// shell, multiline und der Befehlstext. Ab Phase 4 ausserdem Connector und
|
||||
/// Parameter fuer Schritte vom Typ AKTION.
|
||||
konfig Json @default("{}")
|
||||
dokuUrl String?
|
||||
|
||||
abschnitt VorlagenAbschnitt @relation(fields: [abschnittId], references: [id], onDelete: Cascade)
|
||||
ergebnisse Schrittergebnis[]
|
||||
|
||||
@@index([abschnittId, position])
|
||||
}
|
||||
|
||||
// ---- Laeufe ----------------------------------------------------------------
|
||||
|
||||
model Lauf {
|
||||
id String @id @default(cuid())
|
||||
vorlagenVersionId String
|
||||
titel String
|
||||
kundeId String
|
||||
/// Mitarbeiter aus dem Verzeichnis. Ein Lauf ohne ist gueltig – Server,
|
||||
/// Firewall, Patchday.
|
||||
mitarbeiterId String?
|
||||
/// Freitext, wenn die Person noch nicht im Verzeichnis existiert, etwa beim
|
||||
/// Onboarding. Kann spaeter ueber den UPN mit einem Mitarbeiter verknuepft
|
||||
/// werden; beim Verknuepfen wird dieses Feld geleert, weil eine Bedingung in
|
||||
/// der Datenbank beides zugleich ausschliesst. Der urspruengliche Name bleibt
|
||||
/// im Protokoll erhalten.
|
||||
mitarbeiterName String?
|
||||
geraetId String?
|
||||
/// Die beim Start gewaehlte Abteilung. Speist {{ou}} und {{gruppen}}. Steht als
|
||||
/// Bezug und nicht nur als Wert in variablen, damit die Druckansicht eines
|
||||
/// archivierten Laufs reproduzierbar bleibt, auch wenn die Abteilung spaeter
|
||||
/// umbenannt wird.
|
||||
abteilungId String?
|
||||
ticket String?
|
||||
/// Werte der VorlagenVariablen, beim Start erfasst.
|
||||
variablen Json @default("{}")
|
||||
status LaufStatus @default(OFFEN)
|
||||
zugewiesenAnId String?
|
||||
faelligAm DateTime?
|
||||
/// Leer bei einem Lauf, den die Anwendung selbst angelegt hat – wiederkehrende
|
||||
/// Pruefungen und der Start per Webhook kommen in Phase 4. Kein Codepfad darf
|
||||
/// annehmen, dass hier immer ein Mensch steht.
|
||||
erstelltVonId String?
|
||||
/// Bleibt leer, solange der Lauf nur zugewiesen ist. Wird beim ersten Oeffnen
|
||||
/// zur Bearbeitung gesetzt, zusammen mit dem Wechsel auf IN_ARBEIT.
|
||||
gestartetAm DateTime?
|
||||
abgeschlossenAm DateTime?
|
||||
abgeschlossenVonId String?
|
||||
archiviertAm DateTime?
|
||||
archiviertVonId String?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
|
||||
vorlagenVersion VorlagenVersion @relation(fields: [vorlagenVersionId], references: [id], onDelete: Restrict)
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
mitarbeiter Mitarbeiter? @relation(fields: [mitarbeiterId], references: [id], onDelete: Restrict)
|
||||
geraet Geraet? @relation(fields: [geraetId], references: [id], onDelete: Restrict)
|
||||
abteilung Abteilung? @relation(fields: [abteilungId], references: [id], onDelete: Restrict)
|
||||
erstelltVon Benutzer? @relation("LaufErstelltVon", fields: [erstelltVonId], references: [id], onDelete: Restrict)
|
||||
zugewiesenAn Benutzer? @relation("LaufZugewiesenAn", fields: [zugewiesenAnId], references: [id], onDelete: Restrict)
|
||||
abgeschlossenVon Benutzer? @relation("LaufAbgeschlossenVon", fields: [abgeschlossenVonId], references: [id], onDelete: Restrict)
|
||||
archiviertVon Benutzer? @relation("LaufArchiviertVon", fields: [archiviertVonId], references: [id], onDelete: Restrict)
|
||||
|
||||
ergebnisse Schrittergebnis[]
|
||||
kommentare Kommentar[]
|
||||
protokolle Protokoll[]
|
||||
|
||||
@@index([kundeId, status])
|
||||
@@index([geraetId, status])
|
||||
@@index([mitarbeiterId])
|
||||
@@index([zugewiesenAnId, status])
|
||||
@@index([status, faelligAm])
|
||||
@@index([vorlagenVersionId])
|
||||
@@index([abteilungId])
|
||||
}
|
||||
|
||||
model Schrittergebnis {
|
||||
id String @id @default(cuid())
|
||||
laufId String
|
||||
vorlagenSchrittId String
|
||||
zustand SchrittZustand @default(OFFEN)
|
||||
/// Der erfasste Wert, je nach Schritt-Typ. Nimmt ab Phase 4 auch das Ergebnis
|
||||
/// einer Aktion auf: Log, IDs, Zeitstempel.
|
||||
wert Json?
|
||||
/// Pflicht bei NICHT_ZUTREFFEND und FEHLGESCHLAGEN, erzwungen im Service.
|
||||
notiz String?
|
||||
aktualisiertVonId String?
|
||||
aktualisiertAm DateTime @updatedAt
|
||||
erstelltAm DateTime @default(now())
|
||||
|
||||
lauf Lauf @relation(fields: [laufId], references: [id], onDelete: Restrict)
|
||||
vorlagenSchritt VorlagenSchritt @relation(fields: [vorlagenSchrittId], references: [id], onDelete: Restrict)
|
||||
/// Leer, wenn der Schritt von der Anwendung selbst gesetzt wurde. Kein
|
||||
/// Codepfad darf annehmen, dass immer ein Mensch dahintersteht.
|
||||
aktualisiertVon Benutzer? @relation(fields: [aktualisiertVonId], references: [id], onDelete: Restrict)
|
||||
kommentare Kommentar[]
|
||||
|
||||
@@unique([laufId, vorlagenSchrittId])
|
||||
@@index([laufId, zustand])
|
||||
}
|
||||
|
||||
model Kommentar {
|
||||
id String @id @default(cuid())
|
||||
laufId String
|
||||
schrittergebnisId String?
|
||||
benutzerId String
|
||||
rumpf String
|
||||
erstelltAm DateTime @default(now())
|
||||
|
||||
lauf Lauf @relation(fields: [laufId], references: [id], onDelete: Restrict)
|
||||
schrittergebnis Schrittergebnis? @relation(fields: [schrittergebnisId], references: [id], onDelete: Restrict)
|
||||
benutzer Benutzer @relation(fields: [benutzerId], references: [id], onDelete: Restrict)
|
||||
|
||||
@@index([laufId, erstelltAm])
|
||||
@@index([schrittergebnisId])
|
||||
}
|
||||
|
||||
// ---- Protokoll und Sperren -------------------------------------------------
|
||||
|
||||
/// Jede Aenderung an einem Schrittergebnis, jeder Statuswechsel eines Laufs und
|
||||
/// jedes Veroeffentlichen einer Vorlage landet hier. Wird nie geloescht.
|
||||
model Protokoll {
|
||||
id String @id @default(cuid())
|
||||
laufId String?
|
||||
entitaet String
|
||||
entitaetId String
|
||||
aktion String
|
||||
benutzerId String?
|
||||
vorher Json?
|
||||
nachher Json?
|
||||
erstelltAm DateTime @default(now())
|
||||
|
||||
lauf Lauf? @relation(fields: [laufId], references: [id], onDelete: Restrict)
|
||||
benutzer Benutzer? @relation(fields: [benutzerId], references: [id], onDelete: Restrict)
|
||||
|
||||
@@index([laufId, erstelltAm])
|
||||
@@index([entitaet, entitaetId])
|
||||
@@index([erstelltAm])
|
||||
}
|
||||
|
||||
/// Sperre fuer Aufgaben, die nur einmal gleichzeitig laufen duerfen – zuerst der
|
||||
/// LDAP-Abgleich. Liegt in der Datenbank, damit auch mehrere Instanzen der
|
||||
/// Anwendung sich einig sind.
|
||||
model AbgleichSperre {
|
||||
schluessel String @id
|
||||
gehaltenVon String
|
||||
gehaltenSeit DateTime @default(now())
|
||||
laeuftAbAm DateTime
|
||||
|
||||
@@index([laeuftAbAm])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user