Rechte sind jetzt die Grundgröße, eine Rolle ist ein benanntes Bündel davon. Die elf Standardrollen stehen weiterhin im Code und sind unveränderlich – Prüfvermerke und Bescheinigungen berufen sich auf sie. Daneben lassen sich eigene Rollen anlegen. Rolleneditor unter Verwaltung > Rollen & Rechte - Rolle aus einer Standardrolle als Vorlage ableiten oder von Grund auf anlegen - Rechte nach Modul gruppiert ankreuzen, mit Sammelschaltern je Gruppe - Seite des Bauvertrags festlegen (Auftraggeber / Auftragnehmer / Zuarbeit) – bestimmt die Datensicht, unabhängig von der Rechteliste - bearbeiten, stilllegen, löschen; die Rechtematrix zeigt eigene Rollen mit an Grenzen - Administrationsrechte sind nicht über eine Rolle vergebbar, sonst ließe sich über eine selbst gebaute Rolle die Benutzerverwaltung erreichen - eine Rolle ohne jedes Recht wird abgelehnt - eine vergebene Rolle lässt sich weder stilllegen noch löschen - die Vier-Augen-Trennung bleibt unabhängig von der Rolle bestehen Umbau der Rechteauswertung - Rechte werden an genau einer Stelle ermittelt: rechteImProjekt() führt Standardrollen und eigene Rollen zu einer Rechtelage zusammen, die Seiten und Server Actions über rechte.darf() bzw. rechte.pruefe() befragen - die Firmenschranke hängt nun an der Seite des Bauvertrags, die jede Rolle mitbringt, statt an einer festen Liste privilegierter Standardrollen - ProjectMember trägt wahlweise eine Standardrolle oder eine eigene Rolle Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1285 lines
39 KiB
Plaintext
1285 lines
39 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
|
||
}
|
||
|
||
/// Auf welcher Seite des Bauvertrags eine Rolle steht. Bestimmt, ob die
|
||
/// Trägerin oder der Träger das ganze Projekt oder nur die eigene Firma sieht.
|
||
enum RolleSeite {
|
||
AG // Bauherrenseite – sieht das gesamte Projekt
|
||
AN // Auftragnehmerseite – sieht ausschließlich die eigene Firma
|
||
NEUTRAL // Zuarbeit, Prüfung, Mitlesen – ebenfalls auf die eigene Firma beschränkt
|
||
}
|
||
|
||
/// Frei definierbare Rolle. Die elf Standardrollen stehen im Code
|
||
/// (src/lib/rbac.ts) und sind unveränderlich; hier entstehen zusätzliche
|
||
/// Rollen, wahlweise auf Grundlage einer Standardrolle als Vorlage.
|
||
model CustomRole {
|
||
id String @id @default(cuid())
|
||
schluessel String @unique // technischer Name, z.B. "fbue-oberleitung"
|
||
name String
|
||
beschreibung String?
|
||
seite RolleSeite @default(NEUTRAL)
|
||
vorlage Role? // Standardrolle, von der abgeleitet wurde
|
||
rechte String[] @default([]) // Schlüssel aus PERMISSIONS
|
||
aktiv Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
createdById String?
|
||
createdBy User? @relation("CustomRoleAutor", fields: [createdById], references: [id])
|
||
|
||
mitglieder ProjectMember[]
|
||
}
|
||
|
||
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")
|
||
eigeneRollen CustomRole[] @relation("CustomRoleAutor")
|
||
|
||
@@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)
|
||
|
||
// Entweder eine Standardrolle aus dem Code oder eine selbst angelegte.
|
||
// Genau eines von beiden ist gesetzt; darauf achtet die Anwendungsschicht.
|
||
role Role?
|
||
customRoleId String?
|
||
customRole CustomRole? @relation(fields: [customRoleId], references: [id])
|
||
|
||
// Fachbereiche, auf die sich diese Rolle beschränkt (leer = alle)
|
||
fachbereiche String[] @default([])
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([projectId, userId, role, customRoleId])
|
||
@@index([userId])
|
||
@@index([customRoleId])
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════
|
||
// 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])
|
||
}
|