datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model Tenant { id String @id @default(uuid()) name String slug String @unique isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User[] ldapConfig LdapConfig? } enum Role { SUPER_ADMIN ADMIN USER } model User { id String @id @default(uuid()) username String @unique email String @unique passwordHash String? displayName String? role Role @default(USER) isActive Boolean @default(true) mustChangePassword Boolean @default(false) ldapDn String? tenantId String tenant Tenant @relation(fields: [tenantId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastLoginAt DateTime? avatarPath String? passwordResetTokens PasswordResetToken[] @@index([tenantId]) @@index([username]) @@index([email]) } model PasswordResetToken { id String @id @default(uuid()) token String @unique userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) expiresAt DateTime usedAt DateTime? createdAt DateTime @default(now()) } model LdapConfig { id String @id @default(uuid()) tenantId String @unique tenant Tenant @relation(fields: [tenantId], references: [id]) serverUrl String baseDn String bindDn String bindPassword String searchFilter String @default("(objectClass=person)") syncIntervalMin Int @default(60) isActive Boolean @default(true) lastSyncAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt fieldMappings LdapFieldMapping[] } model LdapFieldMapping { id String @id @default(uuid()) ldapConfigId String ldapConfig LdapConfig @relation(fields: [ldapConfigId], references: [id], onDelete: Cascade) ldapField String tesseraField String isDefault Boolean @default(false) createdAt DateTime @default(now()) @@unique([ldapConfigId, ldapField]) } model Module { id String @id @default(uuid()) slug String @unique name String version String category String description Json icon String? isSystem Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt activations TenantModuleActivation[] } model TenantModuleActivation { id String @id @default(uuid()) tenantId String moduleId String isActive Boolean @default(true) activatedAt DateTime @default(now()) module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade) @@unique([tenantId, moduleId]) @@index([tenantId]) } model DashboardLayout { id String @id @default(uuid()) userId String @unique tenantId String layouts Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([tenantId]) } model WidgetInstance { id String @id @default(uuid()) userId String tenantId String widgetType String config Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([tenantId]) } model SearchProvider { id String @id @default(uuid()) userId String? tenantId String? name String urlTemplate String isDefault Boolean @default(false) createdAt DateTime @default(now()) @@index([userId]) } model CalendarSource { id String @id @default(uuid()) userId String tenantId String name String type String // 'caldav' | 'ics' | 'exchange' exchangeMode String? // 'ews' | 'graph' — only for exchange type url String username String? encryptedPassword String? // AES-256-GCM ciphertext (iv:authTag:ciphertext hex) color String? @default("#3B82F6") isVisible Boolean @default(true) syncIntervalMin Int @default(15) lastSyncAt DateTime? lastSyncError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([tenantId]) } model DkvModuleConfig { id String @id @default(uuid()) tenantId String @unique protocol String @default("imap") // 'imap' | 'exchange' host String? port Int? encryption String @default("ssl-tls") // 'none' | 'starttls' | 'ssl-tls' folder String @default("INBOX") senderFilter String? pollIntervalMin Int @default(60) isActive Boolean @default(false) exportRecipient String? vehicleFormatString String @default("{Marke}/{Modell}/{Kennzeichen}") domain String? // Exchange only: Windows domain (optional) encryptedInboxCreds String? // AES-256-GCM: JSON { username, password } encrypted createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([tenantId]) } model DkvVehicleMaster { id String @id @default(uuid()) tenantId String kennzeichen String marke String modell String fahrer String // "Vorname Nachname" createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([tenantId, kennzeichen]) @@index([tenantId]) } model DkvInvoiceHistory { id String @id @default(uuid()) tenantId String datumZeit DateTime @default(now()) rechnungsnummer String anzahlFahrzeuge Int @default(0) anzahlTransaktionen Int @default(0) status String // 'Verarbeitet' | 'Fehler' | 'Versand fehlgeschlagen' errorMessage String? exportFilename String? createdAt DateTime @default(now()) @@index([tenantId]) @@index([datumZeit]) } model SmtpConfig { id String @id @default(uuid()) tenantId String @unique host String port Int @default(587) encryption String @default("starttls") // 'none' | 'starttls' | 'ssl-tls' username String? encryptedPassword String? // AES-256-GCM via CalendarCryptoService fromAddress String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }