Erstimport. Fachliche Grundlage ist die EIU-Ablagestruktur des Auftraggebers (2-EIU-Ablagestruktur.xlsx): 1.106 Zeilen, aufbereitet zu 159 Ordnern und 944 Soll-Unterlagen mit Fachbereichsrelevanz, Aufbewahrungsfrist, Medium und Übergabezeitpunkt. Module - Dokumentationsstand: Soll-Ist je Projekt, Fristen aus dem Übergabezeitpunkt und den Projektterminen, Ampel, CSV-Rückexport im Format der Ursprungsdatei - Dokumentenmanagement mit Versionen, Freigabelauf, SHA-256-Prüfsummen - Bautagebuch nach Ril 809.0301, nach Abschluss gesperrt - Meldungen und Mängel (Behinderung, Bedenken, Mangel, AvL, Unfall) - Verträge, Nachträge, Aufmaße, Stundenlohnzettel mit zeilenweiser Anerkennung - Rechnungsprüfung: automatischer Abgleich gegen Vertrag, anerkanntes Aufmaß, bestätigte Stundenzettel und Vorrechnungen; getrennte Bescheinigung sachlich richtig / rechnerisch richtig / Zahlungsfreigabe - Kommunikation mit Projektkanälen, Erwähnungen und E-Mail-Benachrichtigung - Revisionssicheres Protokoll aller Vorgänge Rechte Rollen nach dem Vorgabeprozess Bauüberwachung (PL, BHV, BÜB, FBÜ, kaufmännische Steuerung, Planung, Auftragnehmer, Prüfer, Betreiber), Zuordnung je Projekt. Auftragnehmer sehen ausschließlich Daten der eigenen Firma; diese Schranke hängt allein an der Rolle. Welche Bereiche ihnen überhaupt angezeigt werden, entscheidet der Bauherr je Projekt über Schalter in den Einstellungen. Betrieb Docker Compose mit Anwendung, PostgreSQL und Mailpit. Keine Named Volumes – alle persistenten Daten liegen als Bind-Mount unter data/ im Compose- Verzeichnis, damit ein Backup ein Kopiervorgang bleibt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1249 lines
37 KiB
Plaintext
1249 lines
37 KiB
Plaintext
// BauDoc Bahn – Datenmodell
|
||
// Fachliche Grundlage: EIU-Ablagestruktur (DB), Vorgabeprozess Bauüberwachung,
|
||
// VV BAU / VV BAU-STE, VOB/B, Ril 809 (Projektmanagement Bau).
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 1 Identität, Organisationen, Rechte
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum CompanyType {
|
||
EIU // Eisenbahninfrastrukturunternehmen / Bauherr (DB InfraGO etc.)
|
||
AUFTRAGNEHMER // Baufirma
|
||
PLANUNGSBUERO
|
||
GUTACHTER // Prüfingenieur, Sachverständiger, TÜV
|
||
SICHERUNGSUNTERNEHMEN
|
||
BEHOERDE // EBA, Kommune, Landesbehörde
|
||
BETREIBER // Anlagenbetreiber / Instandhaltung
|
||
SONSTIGE
|
||
}
|
||
|
||
enum Role {
|
||
ADMIN // Systemadministration
|
||
PROJEKTLEITER // PL
|
||
BAUHERRENVERTRETER // BHV
|
||
BAUUEBERWACHER // BÜB – Bauüberwacher Bahn
|
||
FACHBAUUEBERWACHER // FBÜ
|
||
KAUFMANN // kaufmännische Projektsteuerung, "rechnerisch richtig"
|
||
PLANER
|
||
AUFTRAGNEHMER // Baufirma – nur eigene Vorgänge
|
||
PRUEFER // Gutachter / Sachverständiger
|
||
BETREIBER // liest Betreiberakte
|
||
LESER
|
||
}
|
||
|
||
model Company {
|
||
id String @id @default(cuid())
|
||
name String
|
||
shortName String?
|
||
type CompanyType
|
||
street String?
|
||
zip String?
|
||
city String?
|
||
country String @default("DE")
|
||
ustId String?
|
||
kreditorNr String? // Kreditorennummer für die Rechnungsprüfung
|
||
contactEmail String?
|
||
contactPhone String?
|
||
notes String?
|
||
active Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
|
||
users User[]
|
||
contracts Contract[]
|
||
invoices Invoice[]
|
||
issues Issue[] @relation("IssueAgainstCompany")
|
||
diaryCrews DiaryPersonnel[]
|
||
|
||
@@index([type])
|
||
}
|
||
|
||
model User {
|
||
id String @id @default(cuid())
|
||
email String @unique
|
||
name String
|
||
passwordHash String
|
||
phone String?
|
||
title String? // z.B. "Dipl.-Ing."
|
||
position String? // Freitext, z.B. "Bauüberwacher Bahn KIB"
|
||
active Boolean @default(true)
|
||
isAdmin Boolean @default(false)
|
||
mustChangePw Boolean @default(false)
|
||
lastLoginAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
|
||
companyId String?
|
||
company Company? @relation(fields: [companyId], references: [id])
|
||
|
||
// Benachrichtigungen
|
||
notifyMode NotifyMode @default(SOFORT)
|
||
notifyMentions Boolean @default(true)
|
||
notifyAssignments Boolean @default(true)
|
||
notifyDeadlines Boolean @default(true)
|
||
notifyDigestHour Int @default(7)
|
||
|
||
memberships ProjectMember[]
|
||
sessions Session[]
|
||
uploadedVersions DocumentVersion[]
|
||
reviews DocumentReview[]
|
||
diaryEntries SiteDiaryEntry[] @relation("DiaryAuthor")
|
||
diarySignatures SiteDiaryEntry[] @relation("DiarySignedBy")
|
||
issuesRaised Issue[] @relation("IssueRaisedBy")
|
||
issuesAssigned Issue[] @relation("IssueAssignee")
|
||
issueComments IssueComment[]
|
||
timeSheetsFiled TimeSheet[] @relation("TimeSheetFiledBy")
|
||
timeSheetsChecked TimeSheet[] @relation("TimeSheetCheckedBy")
|
||
measurements Measurement[] @relation("MeasurementCreatedBy")
|
||
invoicesSachlich Invoice[] @relation("InvoiceSachlich")
|
||
invoicesRechn Invoice[] @relation("InvoiceRechnerisch")
|
||
invoicesFreigabe Invoice[] @relation("InvoiceFreigabe")
|
||
messages Message[]
|
||
channelMembers ChannelMember[]
|
||
notifications Notification[]
|
||
auditLogs AuditLog[]
|
||
slotResponsible ProjectDocumentSlot[] @relation("SlotResponsible")
|
||
tasks Task[] @relation("TaskAssignee")
|
||
|
||
@@index([companyId])
|
||
}
|
||
|
||
enum NotifyMode {
|
||
SOFORT
|
||
TAGESZUSAMMENFASSUNG
|
||
AUS
|
||
}
|
||
|
||
model Session {
|
||
id String @id @default(cuid())
|
||
userId String
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
token String @unique
|
||
userAgent String?
|
||
ip String?
|
||
createdAt DateTime @default(now())
|
||
expiresAt DateTime
|
||
|
||
@@index([userId])
|
||
}
|
||
|
||
model ProjectMember {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
userId String
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
role Role
|
||
// Fachbereiche, auf die sich diese Rolle beschränkt (leer = alle)
|
||
fachbereiche String[] @default([])
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([projectId, userId, role])
|
||
@@index([userId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 2 Projekt / Baustelle
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum Bauherr {
|
||
DB_INFRAGO_FAHRWEG // vormals DB Netz
|
||
DB_INFRAGO_BAHNHOF // vormals DB Station&Service
|
||
DB_ENERGIE
|
||
SONSTIGE
|
||
}
|
||
|
||
enum ProjectStatus {
|
||
ANLAGE
|
||
PLANUNG
|
||
VERGABE
|
||
BAU
|
||
INBETRIEBNAHME
|
||
ABNAHME
|
||
GEWAEHRLEISTUNG
|
||
ABGESCHLOSSEN
|
||
RUHEND
|
||
}
|
||
|
||
model Project {
|
||
id String @id @default(cuid())
|
||
nummer String @unique // PSP-Element / Projektnummer
|
||
name String
|
||
kurzname String?
|
||
bauherr Bauherr @default(DB_INFRAGO_FAHRWEG)
|
||
status ProjectStatus @default(ANLAGE)
|
||
regionalbereich String?
|
||
beschreibung String?
|
||
// Fachbereiche, die im Projekt vorkommen – steuert den Soll-Dokumentenkatalog
|
||
fachbereiche String[] @default([])
|
||
budgetPlan Decimal? @db.Decimal(14, 2)
|
||
|
||
// Terminanker – daraus werden die Fristen der Ablagestruktur berechnet
|
||
startgespraech DateTime?
|
||
baubeginn DateTime?
|
||
bauende DateTime?
|
||
inbetriebnahme DateTime?
|
||
bauschlussmeldung DateTime?
|
||
projektabschluss DateTime?
|
||
|
||
// ── Projekteinstellungen ──────────────────────────────────────
|
||
// Was Auftragnehmer in diesem Projekt sehen dürfen. Immer nur die
|
||
// eigene Firma – diese Schalter entscheiden nur, OB der Bereich
|
||
// überhaupt sichtbar ist. Voreinstellung: das, was die Baufirma für
|
||
// ihre eigene Arbeit braucht.
|
||
anSichtVertrag Boolean @default(true) // eigenes LV und eigene Nachträge
|
||
anSichtRechnungen Boolean @default(true) // Prüfstand der eigenen Rechnungen
|
||
anSichtBautagebuch Boolean @default(false) // Bautagebuch des Bauherrn
|
||
anSichtDokumentation Boolean @default(false) // Soll-Ist der Ablagestruktur
|
||
anDarfDokumente Boolean @default(true) // Unterlagen einstellen
|
||
|
||
// Ablauf
|
||
freigabeTrotzBefund Boolean @default(true) // sachlich richtig trotz kritischer Befunde (mit Begründung)
|
||
|
||
archiviert Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
abschnitte ProjectSection[]
|
||
bauwerke Bauwerk[]
|
||
members ProjectMember[]
|
||
slots ProjectDocumentSlot[]
|
||
documents Document[]
|
||
diary SiteDiaryEntry[]
|
||
issues Issue[]
|
||
contracts Contract[]
|
||
timeSheets TimeSheet[]
|
||
measurements Measurement[]
|
||
invoices Invoice[]
|
||
channels Channel[]
|
||
milestones Milestone[]
|
||
tasks Task[]
|
||
warranties Warranty[]
|
||
|
||
@@index([status])
|
||
}
|
||
|
||
model ProjectSection {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
streckenNr String? // VzG-Streckennummer
|
||
streckenName String?
|
||
kmVon String?
|
||
kmBis String?
|
||
betriebsstelle String?
|
||
bemerkung String?
|
||
|
||
@@index([projectId])
|
||
}
|
||
|
||
model Bauwerk {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
bauwerksNr String? // ASB/BWV-Nummer
|
||
bezeichnung String
|
||
typ String? // EÜ, SÜ, Tunnel, Bahnsteig, Stellwerk, Stützbauwerk ...
|
||
fachbereich String?
|
||
kmLage String?
|
||
baujahr Int?
|
||
bemerkung String?
|
||
ueberwachungspflichtig Boolean @default(false)
|
||
|
||
documents Document[]
|
||
warranties Warranty[]
|
||
|
||
@@index([projectId])
|
||
}
|
||
|
||
enum MilestoneType {
|
||
STARTGESPRAECH
|
||
BAUANLAUF
|
||
BAUBEGINN
|
||
ZWISCHEN_IBN
|
||
INBETRIEBNAHME
|
||
BAUENDE
|
||
ABNAHME
|
||
BAUSCHLUSSMELDUNG
|
||
PROJEKTABSCHLUSS
|
||
SONSTIGES
|
||
}
|
||
|
||
model Milestone {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
typ MilestoneType
|
||
titel String
|
||
geplant DateTime?
|
||
ist DateTime?
|
||
bemerkung String?
|
||
|
||
@@index([projectId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 3 Ablagestruktur (Katalog) und Dokumentenmanagement
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum Akte {
|
||
P // Planungsakte
|
||
B // Baudokumentation / Bauakten
|
||
K // Kaufmännische Akte
|
||
}
|
||
|
||
/// Ordnerknoten der EIU-Ablagestruktur – aus der Excel des Auftraggebers.
|
||
model FilingNode {
|
||
code String @id // z.B. "B.I.2.2"
|
||
parentCode String?
|
||
parent FilingNode? @relation("FilingTree", fields: [parentCode], references: [code])
|
||
children FilingNode[] @relation("FilingTree")
|
||
akte Akte
|
||
level Int
|
||
titel String
|
||
ril String?
|
||
sortIndex Int
|
||
|
||
requirements DocumentRequirement[]
|
||
documents Document[]
|
||
|
||
@@index([parentCode])
|
||
@@index([akte])
|
||
}
|
||
|
||
enum Uebergabezeitpunkt {
|
||
INBETRIEBNAHME
|
||
BAUAKTE_II_IBN
|
||
IBN_PLUS_3M
|
||
IBN_PLUS_6M
|
||
BAUAKTE_I_BAUENDE_6M
|
||
PROJEKTABSCHLUSS
|
||
}
|
||
|
||
enum Medium {
|
||
MIKROFICHE
|
||
DIGITAL
|
||
PAPIERAKTE
|
||
}
|
||
|
||
/// Einzelne Soll-Unterlage aus der Ablagestruktur (945 Positionen).
|
||
model DocumentRequirement {
|
||
id String @id @default(cuid())
|
||
ordnung Int @unique // laufende Nummer aus der Excel
|
||
nodeCode String
|
||
node FilingNode @relation(fields: [nodeCode], references: [code], onDelete: Cascade)
|
||
titel String
|
||
ril String?
|
||
istUnterpunkt Boolean @default(false)
|
||
|
||
relevantNetz Boolean @default(false)
|
||
relevantBahnhof Boolean @default(false)
|
||
fachbereiche String[] @default([]) // vereinigte Fachbereichsliste
|
||
|
||
aufbewahrungNutzungsdauer Boolean @default(false)
|
||
aufbewahrungJahre String?
|
||
medien Medium[] @default([])
|
||
uebergabe Uebergabezeitpunkt[] @default([])
|
||
bemerkung String?
|
||
|
||
slots ProjectDocumentSlot[]
|
||
|
||
@@index([nodeCode])
|
||
}
|
||
|
||
enum SlotStatus {
|
||
OFFEN
|
||
ANGEFORDERT
|
||
IN_ARBEIT
|
||
EINGEREICHT
|
||
IN_PRUEFUNG
|
||
FREIGEGEBEN
|
||
ABGELEHNT
|
||
ENTFAELLT
|
||
}
|
||
|
||
/// Soll-Ist-Abgleich: pro Projekt eine Zeile je relevanter Soll-Unterlage.
|
||
/// Das ist die lebende Version der Excel-Prüfliste.
|
||
model ProjectDocumentSlot {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
requirementId String
|
||
requirement DocumentRequirement @relation(fields: [requirementId], references: [id], onDelete: Cascade)
|
||
|
||
status SlotStatus @default(OFFEN)
|
||
responsibleId String?
|
||
responsible User? @relation("SlotResponsible", fields: [responsibleId], references: [id])
|
||
frist DateTime? // berechnet aus Übergabezeitpunkt + Projekttermine
|
||
eingangAm DateTime? // "Datum Eintreffen der Unterlagen"
|
||
entfalltGrund String?
|
||
bemerkung String?
|
||
updatedAt DateTime @updatedAt
|
||
|
||
documents Document[]
|
||
|
||
@@unique([projectId, requirementId])
|
||
@@index([projectId, status])
|
||
}
|
||
|
||
enum DocumentStatus {
|
||
ENTWURF
|
||
EINGEREICHT
|
||
IN_PRUEFUNG
|
||
FREIGEGEBEN
|
||
ABGELEHNT
|
||
ZURUECKGEZOGEN
|
||
ARCHIVIERT
|
||
}
|
||
|
||
enum Vertraulichkeit {
|
||
OFFEN // alle Projektbeteiligten
|
||
INTERN // nur Bauherrenseite (EIU)
|
||
VERTRAULICH // nur explizit Berechtigte
|
||
}
|
||
|
||
model Document {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
nodeCode String?
|
||
node FilingNode? @relation(fields: [nodeCode], references: [code])
|
||
slotId String?
|
||
slot ProjectDocumentSlot? @relation(fields: [slotId], references: [id])
|
||
|
||
bauwerkId String?
|
||
bauwerk Bauwerk? @relation(fields: [bauwerkId], references: [id])
|
||
|
||
titel String
|
||
dokumentNr String?
|
||
fachbereich String?
|
||
status DocumentStatus @default(ENTWURF)
|
||
vertraulich Vertraulichkeit @default(OFFEN)
|
||
beschreibung String?
|
||
schlagworte String[] @default([])
|
||
planNr String?
|
||
planStand String?
|
||
gueltigAb DateTime?
|
||
aufbewahrungBis DateTime?
|
||
|
||
createdById String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
versions DocumentVersion[]
|
||
reviews DocumentReview[]
|
||
invoices Invoice[]
|
||
links DocumentLink[] @relation("LinkFrom")
|
||
linkedBy DocumentLink[] @relation("LinkTo")
|
||
|
||
@@index([projectId, status])
|
||
@@index([nodeCode])
|
||
@@index([slotId])
|
||
}
|
||
|
||
model DocumentVersion {
|
||
id String @id @default(cuid())
|
||
documentId String
|
||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||
version Int
|
||
dateiname String
|
||
pfad String // relativ zu STORAGE_DIR
|
||
mimeType String
|
||
groesse Int
|
||
sha256 String
|
||
kommentar String?
|
||
uploadedById String?
|
||
uploadedBy User? @relation(fields: [uploadedById], references: [id])
|
||
uploadedAt DateTime @default(now())
|
||
|
||
@@unique([documentId, version])
|
||
@@index([sha256])
|
||
}
|
||
|
||
enum PruefArt {
|
||
FACHTECHNISCHE_PRUEFUNG
|
||
FORMALE_PRUEFUNG
|
||
FREIGABE
|
||
KENNTNISNAHME
|
||
SACHLICHE_PRUEFUNG
|
||
}
|
||
|
||
enum PruefErgebnis {
|
||
OFFEN
|
||
FREIGEGEBEN
|
||
FREIGEGEBEN_MIT_AUFLAGEN
|
||
ABGELEHNT
|
||
}
|
||
|
||
model DocumentReview {
|
||
id String @id @default(cuid())
|
||
documentId String
|
||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||
version Int?
|
||
art PruefArt
|
||
ergebnis PruefErgebnis @default(OFFEN)
|
||
reviewerId String
|
||
reviewer User @relation(fields: [reviewerId], references: [id])
|
||
faelligAm DateTime?
|
||
erledigtAm DateTime?
|
||
kommentar String?
|
||
auflagen String?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([documentId])
|
||
@@index([reviewerId, ergebnis])
|
||
}
|
||
|
||
model DocumentLink {
|
||
id String @id @default(cuid())
|
||
fromId String
|
||
from Document @relation("LinkFrom", fields: [fromId], references: [id], onDelete: Cascade)
|
||
toId String
|
||
to Document @relation("LinkTo", fields: [toId], references: [id], onDelete: Cascade)
|
||
art String? // "ersetzt", "gehört zu", "Prüfbericht zu" ...
|
||
|
||
@@unique([fromId, toId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 4 Bautagebuch (angelehnt an eBTB / Ril 809.0301V01)
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum DiaryStatus {
|
||
ENTWURF
|
||
ABGESCHLOSSEN
|
||
GEGENGEZEICHNET
|
||
}
|
||
|
||
model SiteDiaryEntry {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
datum DateTime @db.Date
|
||
lfdNr Int
|
||
|
||
authorId String
|
||
author User @relation("DiaryAuthor", fields: [authorId], references: [id])
|
||
|
||
wetterFrueh String?
|
||
wetterMittag String?
|
||
wetterAbend String?
|
||
tempMin Int?
|
||
tempMax Int?
|
||
windRegen String?
|
||
|
||
arbeitszeitVon String?
|
||
arbeitszeitBis String?
|
||
betra String? // Betriebs- und Bauanweisung
|
||
sperrpause String?
|
||
sperrungVon DateTime?
|
||
sperrungBis DateTime?
|
||
|
||
leistungen String? // ausgeführte Arbeiten
|
||
besondereVorkommnisse String?
|
||
behinderungen String?
|
||
anordnungen String?
|
||
einweisungen String? // Ersteinweisung / Baustellenbelehrung
|
||
sicherungsmassnahmen String?
|
||
bemerkungen String?
|
||
|
||
status DiaryStatus @default(ENTWURF)
|
||
signedById String?
|
||
signedBy User? @relation("DiarySignedBy", fields: [signedById], references: [id])
|
||
signedAt DateTime?
|
||
gesperrtAm DateTime? // revisionssicher: danach nur noch Nachträge
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
personal DiaryPersonnel[]
|
||
geraete DiaryEquipment[]
|
||
issues Issue[]
|
||
timeSheets TimeSheet[]
|
||
|
||
@@unique([projectId, datum, lfdNr])
|
||
@@index([projectId, datum])
|
||
}
|
||
|
||
model DiaryPersonnel {
|
||
id String @id @default(cuid())
|
||
entryId String
|
||
entry SiteDiaryEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
|
||
companyId String?
|
||
company Company? @relation(fields: [companyId], references: [id])
|
||
gewerk String?
|
||
taetigkeit String?
|
||
anzahl Int @default(0)
|
||
stunden Decimal @default(0) @db.Decimal(8, 2)
|
||
|
||
@@index([entryId])
|
||
}
|
||
|
||
model DiaryEquipment {
|
||
id String @id @default(cuid())
|
||
entryId String
|
||
entry SiteDiaryEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
|
||
bezeichnung String
|
||
anzahl Int @default(1)
|
||
stunden Decimal @default(0) @db.Decimal(8, 2)
|
||
bemerkung String?
|
||
|
||
@@index([entryId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 5 Meldungen / Vorgänge / Mängel
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum IssueType {
|
||
BEHINDERUNGSANZEIGE
|
||
BEDENKENANZEIGE
|
||
MANGELANZEIGE
|
||
MEHRKOSTEN_AVL // Anordnung von Leistungen
|
||
SCHADENSMELDUNG
|
||
UNFALLANZEIGE
|
||
SOFORTMELDUNG
|
||
VERTRAGSABWEICHUNG
|
||
BETREIBERHINWEIS
|
||
SONSTIGES
|
||
}
|
||
|
||
enum IssueStatus {
|
||
OFFEN
|
||
IN_BEARBEITUNG
|
||
BEHOBEN_GEMELDET
|
||
NACHKONTROLLE
|
||
ERLEDIGT
|
||
ABGELEHNT
|
||
}
|
||
|
||
enum Severity {
|
||
GERING
|
||
MITTEL
|
||
HOCH
|
||
SICHERHEITSRELEVANT
|
||
}
|
||
|
||
model Issue {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
nr Int
|
||
typ IssueType
|
||
titel String
|
||
beschreibung String?
|
||
status IssueStatus @default(OFFEN)
|
||
schwere Severity @default(MITTEL)
|
||
|
||
ortKm String?
|
||
bauteil String?
|
||
fachbereich String?
|
||
|
||
raisedById String
|
||
raisedBy User @relation("IssueRaisedBy", fields: [raisedById], references: [id])
|
||
assigneeId String?
|
||
assignee User? @relation("IssueAssignee", fields: [assigneeId], references: [id])
|
||
gegenFirmaId String?
|
||
gegenFirma Company? @relation("IssueAgainstCompany", fields: [gegenFirmaId], references: [id])
|
||
contractId String?
|
||
contract Contract? @relation(fields: [contractId], references: [id])
|
||
diaryEntryId String?
|
||
diaryEntry SiteDiaryEntry? @relation(fields: [diaryEntryId], references: [id])
|
||
|
||
gemeldetAm DateTime @default(now())
|
||
fristAm DateTime?
|
||
erledigtAm DateTime?
|
||
kostenrelevant Boolean @default(false)
|
||
terminrelevant Boolean @default(false)
|
||
geschaetzteKosten Decimal? @db.Decimal(12, 2)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
kommentare IssueComment[]
|
||
|
||
@@unique([projectId, nr])
|
||
@@index([projectId, status])
|
||
}
|
||
|
||
model IssueComment {
|
||
id String @id @default(cuid())
|
||
issueId String
|
||
issue Issue @relation(fields: [issueId], references: [id], onDelete: Cascade)
|
||
authorId String
|
||
author User @relation(fields: [authorId], references: [id])
|
||
text String
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([issueId])
|
||
}
|
||
|
||
/// Fristenblatt zu Mängelansprüchen (Ablagestruktur B 6.6)
|
||
model Warranty {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
bauwerkId String?
|
||
bauwerk Bauwerk? @relation(fields: [bauwerkId], references: [id])
|
||
contractId String?
|
||
contract Contract? @relation(fields: [contractId], references: [id])
|
||
gegenstand String
|
||
abnahmeAm DateTime
|
||
fristJahre Int @default(4)
|
||
fristEndeAm DateTime
|
||
verantwortlich String?
|
||
buergschaft Decimal? @db.Decimal(12, 2)
|
||
status String @default("LAUFEND")
|
||
bemerkung String?
|
||
|
||
@@index([projectId, fristEndeAm])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 6 Verträge, Nachträge, LV
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum ContractType {
|
||
BAUVERTRAG
|
||
INGENIEURVERTRAG
|
||
RAHMENVERTRAG
|
||
SICHERUNGSLEISTUNG
|
||
LIEFERVERTRAG
|
||
SONSTIGER
|
||
}
|
||
|
||
enum ContractStatus {
|
||
ENTWURF
|
||
BEAUFTRAGT
|
||
IN_ABWICKLUNG
|
||
ABGENOMMEN
|
||
SCHLUSSGERECHNET
|
||
ABGESCHLOSSEN
|
||
GEKUENDIGT
|
||
}
|
||
|
||
model Contract {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
companyId String
|
||
company Company @relation(fields: [companyId], references: [id])
|
||
|
||
nummer String
|
||
bezeichnung String
|
||
typ ContractType @default(BAUVERTRAG)
|
||
status ContractStatus @default(BEAUFTRAGT)
|
||
vergabeart String?
|
||
auftragssumme Decimal @default(0) @db.Decimal(14, 2)
|
||
waehrung String @default("EUR")
|
||
beauftragtAm DateTime?
|
||
ausfuehrungVon DateTime?
|
||
ausfuehrungBis DateTime?
|
||
gewaehrleistungJahre Int @default(4)
|
||
sicherheitseinbehaltProzent Decimal @default(0) @db.Decimal(5, 2)
|
||
skontoProzent Decimal @default(0) @db.Decimal(5, 2)
|
||
skontoTage Int @default(0)
|
||
zahlungszielTage Int @default(30)
|
||
vertragsverantwortlich String?
|
||
bemerkung String?
|
||
createdAt DateTime @default(now())
|
||
|
||
positionen ContractItem[]
|
||
nachtraege ChangeOrder[]
|
||
timeSheets TimeSheet[]
|
||
measurements Measurement[]
|
||
invoices Invoice[]
|
||
issues Issue[]
|
||
warranties Warranty[]
|
||
|
||
@@unique([projectId, nummer])
|
||
@@index([companyId])
|
||
}
|
||
|
||
model ContractItem {
|
||
id String @id @default(cuid())
|
||
contractId String
|
||
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||
changeOrderId String?
|
||
changeOrder ChangeOrder? @relation(fields: [changeOrderId], references: [id])
|
||
|
||
oz String // Ordnungszahl der LV-Position
|
||
kurztext String
|
||
langtext String?
|
||
einheit String @default("St")
|
||
menge Decimal @default(0) @db.Decimal(14, 3)
|
||
einheitspreis Decimal @default(0) @db.Decimal(12, 2)
|
||
istStundenlohn Boolean @default(false)
|
||
istEventual Boolean @default(false)
|
||
|
||
measurementLines MeasurementLine[]
|
||
invoiceLines InvoiceLine[]
|
||
|
||
@@unique([contractId, oz])
|
||
@@index([contractId])
|
||
}
|
||
|
||
enum ChangeOrderStatus {
|
||
ANGEMELDET
|
||
ANGEBOT_EINGEGANGEN
|
||
IN_PRUEFUNG_BUEW
|
||
STELLUNGNAHME_ABGEGEBEN
|
||
BEAUFTRAGT
|
||
ABGELEHNT
|
||
ZURUECKGEZOGEN
|
||
}
|
||
|
||
model ChangeOrder {
|
||
id String @id @default(cuid())
|
||
contractId String
|
||
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||
nummer String
|
||
titel String
|
||
begruendung String?
|
||
rechtsgrundlage String? // § 2 Abs. 5 / 6 VOB/B, § 1 Abs. 3 / 4 VOB/B
|
||
angebotssumme Decimal? @db.Decimal(14, 2)
|
||
anerkannteSumme Decimal? @db.Decimal(14, 2)
|
||
status ChangeOrderStatus @default(ANGEMELDET)
|
||
angemeldetAm DateTime?
|
||
geprueftAm DateTime?
|
||
beauftragtAm DateTime?
|
||
stellungnahme String?
|
||
|
||
positionen ContractItem[]
|
||
invoiceLines InvoiceLine[]
|
||
|
||
@@unique([contractId, nummer])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 7 Aufmaß und Stundenlohnzettel
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum MeasurementArt {
|
||
ABRECHNUNGSZEICHNUNG
|
||
OERTLICHES_AUFMASS
|
||
AUFMASSBLATT
|
||
}
|
||
|
||
enum PruefStatus {
|
||
EINGEREICHT
|
||
IN_PRUEFUNG
|
||
ANERKANNT
|
||
TEILWEISE_ANERKANNT
|
||
ABGELEHNT
|
||
}
|
||
|
||
model Measurement {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
contractId String
|
||
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||
nummer String
|
||
art MeasurementArt @default(AUFMASSBLATT)
|
||
datum DateTime
|
||
ortBauteil String?
|
||
status PruefStatus @default(EINGEREICHT)
|
||
createdById String
|
||
createdBy User @relation("MeasurementCreatedBy", fields: [createdById], references: [id])
|
||
geprueftAm DateTime?
|
||
bemerkung String?
|
||
createdAt DateTime @default(now())
|
||
|
||
zeilen MeasurementLine[]
|
||
|
||
@@unique([contractId, nummer])
|
||
@@index([projectId])
|
||
}
|
||
|
||
model MeasurementLine {
|
||
id String @id @default(cuid())
|
||
measurementId String
|
||
measurement Measurement @relation(fields: [measurementId], references: [id], onDelete: Cascade)
|
||
contractItemId String?
|
||
contractItem ContractItem? @relation(fields: [contractItemId], references: [id])
|
||
beschreibung String?
|
||
formel String? // REB-konforme Berechnung, z.B. "12,50 * 3,20"
|
||
menge Decimal @default(0) @db.Decimal(14, 3)
|
||
mengeAnerkannt Decimal? @db.Decimal(14, 3)
|
||
|
||
@@index([measurementId])
|
||
}
|
||
|
||
enum LohnArt {
|
||
FACHARBEITER
|
||
VORARBEITER
|
||
POLIER
|
||
HELFER
|
||
MASCHINIST
|
||
SICHERUNGSPOSTEN
|
||
GERAET
|
||
SONSTIGES
|
||
}
|
||
|
||
/// Stundenlohnzettel (Ablagestruktur K 3.4) – Grundlage der Rechnungsprüfung.
|
||
model TimeSheet {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
contractId String
|
||
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||
nummer String
|
||
datum DateTime @db.Date
|
||
leistung String
|
||
ortKm String?
|
||
bezugNachtrag String?
|
||
bezugPosition String?
|
||
|
||
diaryEntryId String?
|
||
diaryEntry SiteDiaryEntry? @relation(fields: [diaryEntryId], references: [id])
|
||
|
||
status PruefStatus @default(EINGEREICHT)
|
||
filedById String
|
||
filedBy User @relation("TimeSheetFiledBy", fields: [filedById], references: [id])
|
||
checkedById String?
|
||
checkedBy User? @relation("TimeSheetCheckedBy", fields: [checkedById], references: [id])
|
||
geprueftAm DateTime?
|
||
pruefkommentar String?
|
||
createdAt DateTime @default(now())
|
||
|
||
zeilen TimeSheetLine[]
|
||
invoiceLines InvoiceLine[]
|
||
|
||
@@unique([contractId, nummer])
|
||
@@index([projectId, datum])
|
||
@@index([status])
|
||
}
|
||
|
||
model TimeSheetLine {
|
||
id String @id @default(cuid())
|
||
timeSheetId String
|
||
timeSheet TimeSheet @relation(fields: [timeSheetId], references: [id], onDelete: Cascade)
|
||
name String? // Person oder Gerätebezeichnung
|
||
art LohnArt @default(FACHARBEITER)
|
||
stundenGemeldet Decimal @default(0) @db.Decimal(8, 2)
|
||
stundenAnerkannt Decimal? @db.Decimal(8, 2)
|
||
stundensatz Decimal @default(0) @db.Decimal(10, 2)
|
||
zuschlagProzent Decimal @default(0) @db.Decimal(5, 2)
|
||
zuschlagGrund String? // Nacht, Sonntag, Feiertag, Sperrpause
|
||
bemerkung String?
|
||
|
||
@@index([timeSheetId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 8 Rechnungsprüfung
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum InvoiceType {
|
||
ABSCHLAGSRECHNUNG
|
||
TEILSCHLUSSRECHNUNG
|
||
SCHLUSSRECHNUNG
|
||
NACHTRAGSRECHNUNG
|
||
STUNDENLOHNRECHNUNG
|
||
GUTSCHRIFT
|
||
}
|
||
|
||
enum InvoiceStatus {
|
||
EINGEGANGEN
|
||
IN_PRUEFUNG
|
||
RUECKFRAGE
|
||
SACHLICH_RICHTIG // Bauüberwachung
|
||
RECHNERISCH_RICHTIG // kaufmännisch
|
||
FREIGEGEBEN
|
||
ZURUECKGEWIESEN
|
||
BEZAHLT
|
||
}
|
||
|
||
model Invoice {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
contractId String?
|
||
contract Contract? @relation(fields: [contractId], references: [id])
|
||
companyId String
|
||
company Company @relation(fields: [companyId], references: [id])
|
||
|
||
rechnungsNr String
|
||
typ InvoiceType @default(ABSCHLAGSRECHNUNG)
|
||
status InvoiceStatus @default(EINGEGANGEN)
|
||
rechnungsdatum DateTime
|
||
eingangsdatum DateTime @default(now())
|
||
leistungVon DateTime?
|
||
leistungBis DateTime?
|
||
|
||
nettoBetrag Decimal @default(0) @db.Decimal(14, 2)
|
||
ustSatz Decimal @default(19) @db.Decimal(5, 2)
|
||
ustBetrag Decimal @default(0) @db.Decimal(14, 2)
|
||
bruttoBetrag Decimal @default(0) @db.Decimal(14, 2)
|
||
// kumulierte Vorauszahlungen bei Abschlagsrechnungen
|
||
bereitsGezahlt Decimal @default(0) @db.Decimal(14, 2)
|
||
einbehalt Decimal @default(0) @db.Decimal(14, 2)
|
||
skonto Decimal @default(0) @db.Decimal(14, 2)
|
||
zahlbarBis DateTime?
|
||
|
||
// anerkannte Summe nach Prüfung
|
||
anerkanntNetto Decimal? @db.Decimal(14, 2)
|
||
kuerzungNetto Decimal @default(0) @db.Decimal(14, 2)
|
||
kuerzungGrund String?
|
||
|
||
sachlichById String?
|
||
sachlichBy User? @relation("InvoiceSachlich", fields: [sachlichById], references: [id])
|
||
sachlichAm DateTime?
|
||
rechnerischById String?
|
||
rechnerischBy User? @relation("InvoiceRechnerisch", fields: [rechnerischById], references: [id])
|
||
rechnerischAm DateTime?
|
||
freigabeById String?
|
||
freigabeBy User? @relation("InvoiceFreigabe", fields: [freigabeById], references: [id])
|
||
freigabeAm DateTime?
|
||
|
||
documentId String?
|
||
document Document? @relation(fields: [documentId], references: [id])
|
||
|
||
letzterPrueflauf DateTime?
|
||
bemerkung String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
zeilen InvoiceLine[]
|
||
befunde InvoiceFinding[]
|
||
|
||
@@unique([companyId, rechnungsNr])
|
||
@@index([projectId, status])
|
||
}
|
||
|
||
enum InvoiceLineKind {
|
||
LV_POSITION
|
||
STUNDENLOHN
|
||
NACHTRAG
|
||
MATERIAL
|
||
SONSTIGES
|
||
}
|
||
|
||
model InvoiceLine {
|
||
id String @id @default(cuid())
|
||
invoiceId String
|
||
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||
position Int
|
||
art InvoiceLineKind @default(LV_POSITION)
|
||
oz String?
|
||
bezeichnung String
|
||
einheit String @default("St")
|
||
menge Decimal @default(0) @db.Decimal(14, 3)
|
||
einheitspreis Decimal @default(0) @db.Decimal(12, 2)
|
||
betrag Decimal @default(0) @db.Decimal(14, 2)
|
||
|
||
contractItemId String?
|
||
contractItem ContractItem? @relation(fields: [contractItemId], references: [id])
|
||
timeSheetId String?
|
||
timeSheet TimeSheet? @relation(fields: [timeSheetId], references: [id])
|
||
changeOrderId String?
|
||
changeOrder ChangeOrder? @relation(fields: [changeOrderId], references: [id])
|
||
|
||
mengeAnerkannt Decimal? @db.Decimal(14, 3)
|
||
betragAnerkannt Decimal? @db.Decimal(14, 2)
|
||
|
||
befunde InvoiceFinding[]
|
||
|
||
@@index([invoiceId])
|
||
}
|
||
|
||
enum FindingKind {
|
||
KEIN_VERTRAGSBEZUG
|
||
PREIS_ABWEICHUNG
|
||
MENGE_OHNE_AUFMASS
|
||
MENGE_UEBER_AUFMASS
|
||
STUNDEN_OHNE_ZETTEL
|
||
STUNDEN_UEBER_ZETTEL
|
||
STUNDENSATZ_ABWEICHUNG
|
||
ZETTEL_NICHT_ANERKANNT
|
||
DOPPELABRECHNUNG
|
||
NACHTRAG_NICHT_BEAUFTRAGT
|
||
RECHENFEHLER
|
||
SUMME_UEBER_AUFTRAG
|
||
ZUSCHLAG_UNBELEGT
|
||
HINWEIS
|
||
}
|
||
|
||
enum FindingSeverity {
|
||
INFO
|
||
WARNUNG
|
||
KRITISCH
|
||
}
|
||
|
||
/// Ergebnis des automatischen Abgleichs Rechnung ↔ Vertrag/Aufmaß/Stundenzettel.
|
||
model InvoiceFinding {
|
||
id String @id @default(cuid())
|
||
invoiceId String
|
||
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||
lineId String?
|
||
line InvoiceLine? @relation(fields: [lineId], references: [id], onDelete: Cascade)
|
||
|
||
art FindingKind
|
||
schwere FindingSeverity @default(WARNUNG)
|
||
meldung String
|
||
erwartet String?
|
||
gefunden String?
|
||
differenz Decimal? @db.Decimal(14, 2)
|
||
|
||
erledigt Boolean @default(false)
|
||
erledigtGrund String?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([invoiceId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 9 Kommunikation und Benachrichtigung
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum ChannelKind {
|
||
PROJEKT
|
||
FACHBEREICH
|
||
THEMA
|
||
DIREKT
|
||
}
|
||
|
||
model Channel {
|
||
id String @id @default(cuid())
|
||
projectId String?
|
||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
name String
|
||
beschreibung String?
|
||
kind ChannelKind @default(THEMA)
|
||
privat Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
|
||
mitglieder ChannelMember[]
|
||
nachrichten Message[]
|
||
|
||
@@index([projectId])
|
||
}
|
||
|
||
model ChannelMember {
|
||
id String @id @default(cuid())
|
||
channelId String
|
||
channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade)
|
||
userId String
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
gelesenBis DateTime?
|
||
stumm Boolean @default(false)
|
||
|
||
@@unique([channelId, userId])
|
||
@@index([userId])
|
||
}
|
||
|
||
model Message {
|
||
id String @id @default(cuid())
|
||
channelId String
|
||
channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade)
|
||
authorId String
|
||
author User @relation(fields: [authorId], references: [id])
|
||
text String
|
||
parentId String?
|
||
parent Message? @relation("Thread", fields: [parentId], references: [id])
|
||
antworten Message[] @relation("Thread")
|
||
|
||
// Verknüpfung mit einem Fachobjekt: DOKUMENT | VORGANG | RECHNUNG | BAUTAGEBUCH
|
||
refTyp String?
|
||
refId String?
|
||
|
||
mentions String[] @default([]) // User-IDs
|
||
anhaenge MessageAttachment[]
|
||
bearbeitetAm DateTime?
|
||
geloeschtAm DateTime?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([channelId, createdAt])
|
||
}
|
||
|
||
model MessageAttachment {
|
||
id String @id @default(cuid())
|
||
messageId String
|
||
message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
||
dateiname String
|
||
pfad String
|
||
mimeType String
|
||
groesse Int
|
||
}
|
||
|
||
enum NotificationType {
|
||
ERWAEHNUNG
|
||
NACHRICHT
|
||
ZUWEISUNG
|
||
FRIST
|
||
FREIGABE_ANGEFORDERT
|
||
DOKUMENT_NEU
|
||
RECHNUNG_BEFUND
|
||
MANGEL
|
||
SYSTEM
|
||
}
|
||
|
||
model Notification {
|
||
id String @id @default(cuid())
|
||
userId String
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
typ NotificationType
|
||
titel String
|
||
text String?
|
||
link String?
|
||
projectId String?
|
||
gelesenAm DateTime?
|
||
gemailtAm DateTime?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([userId, gelesenAm])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 10 Aufgaben und Protokollierung
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
|
||
enum TaskStatus {
|
||
OFFEN
|
||
IN_ARBEIT
|
||
ERLEDIGT
|
||
VERWORFEN
|
||
}
|
||
|
||
model Task {
|
||
id String @id @default(cuid())
|
||
projectId String
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
titel String
|
||
beschreibung String?
|
||
status TaskStatus @default(OFFEN)
|
||
faelligAm DateTime?
|
||
assigneeId String?
|
||
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id])
|
||
refTyp String?
|
||
refId String?
|
||
createdAt DateTime @default(now())
|
||
erledigtAm DateTime?
|
||
|
||
@@index([projectId, status])
|
||
}
|
||
|
||
model AuditLog {
|
||
id String @id @default(cuid())
|
||
userId String?
|
||
user User? @relation(fields: [userId], references: [id])
|
||
aktion String // CREATE | UPDATE | DELETE | LOGIN | DOWNLOAD | FREIGABE ...
|
||
entitaet String
|
||
entitaetId String?
|
||
projectId String?
|
||
details Json?
|
||
ip String?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([entitaet, entitaetId])
|
||
@@index([projectId, createdAt])
|
||
}
|