opencrm/backend/prisma/schema.prisma

624 lines
23 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
// ==================== APP SETTINGS ====================
model AppSetting {
id Int @id @default(autoincrement())
key String @unique
value String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== USERS & AUTH ====================
model User {
id Int @id @default(autoincrement())
email String @unique
password String
firstName String
lastName String
isActive Boolean @default(true)
customerId Int? @unique
customer Customer? @relation(fields: [customerId], references: [id])
roles UserRole[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Role {
id Int @id @default(autoincrement())
name String @unique
description String?
permissions RolePermission[]
users UserRole[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Permission {
id Int @id @default(autoincrement())
resource String
action String
roles RolePermission[]
@@unique([resource, action])
}
model RolePermission {
roleId Int
permissionId Int
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
@@id([roleId, permissionId])
}
model UserRole {
userId Int
roleId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
@@id([userId, roleId])
}
// ==================== CUSTOMERS ====================
enum CustomerType {
PRIVATE
BUSINESS
}
model Customer {
id Int @id @default(autoincrement())
customerNumber String @unique
type CustomerType @default(PRIVATE)
salutation String?
firstName String
lastName String
companyName String?
foundingDate DateTime? // Gründungsdatum (für Firmen)
birthDate DateTime?
birthPlace String?
email String?
phone String?
mobile String?
taxNumber String?
businessRegistrationPath String? // PDF-Pfad zur Gewerbeanmeldung
commercialRegisterPath String? // PDF-Pfad zum Handelsregisterauszug
commercialRegisterNumber String? // Handelsregisternummer (Text)
privacyPolicyPath String? // PDF-Pfad zur Datenschutzerklärung (für alle Kunden)
notes String? @db.Text
// ===== Portal-Zugangsdaten =====
portalEnabled Boolean @default(false) // Portal aktiviert?
portalEmail String? @unique // Portal-Login E-Mail
portalPasswordHash String? // Gehashtes Passwort (für Login)
portalPasswordEncrypted String? // Verschlüsseltes Passwort (für Anzeige)
portalLastLogin DateTime? // Letzte Anmeldung
user User?
addresses Address[]
bankCards BankCard[]
identityDocuments IdentityDocument[]
meters Meter[]
stressfreiEmails StressfreiEmail[]
contracts Contract[]
// Vertreter-Beziehungen (Kunde kann für andere Kunden handeln)
representingFor CustomerRepresentative[] @relation("RepresentativeCustomer")
representedBy CustomerRepresentative[] @relation("RepresentedCustomer")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== CUSTOMER REPRESENTATIVES ====================
// Vertretungsbeziehung: Ein Kunde kann die Verträge eines anderen Kunden einsehen
// z.B. Sohn (representativeId) kann Verträge der Mutter (customerId) sehen
model CustomerRepresentative {
id Int @id @default(autoincrement())
customerId Int // Der Kunde, dessen Verträge eingesehen werden (z.B. Mutter)
customer Customer @relation("RepresentedCustomer", fields: [customerId], references: [id], onDelete: Cascade)
representativeId Int // Der Kunde, der einsehen darf (z.B. Sohn)
representative Customer @relation("RepresentativeCustomer", fields: [representativeId], references: [id], onDelete: Cascade)
notes String? // Notizen zur Vertretung
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([customerId, representativeId]) // Keine doppelten Einträge
}
// ==================== ADDRESSES ====================
enum AddressType {
DELIVERY_RESIDENCE
BILLING
}
model Address {
id Int @id @default(autoincrement())
customerId Int
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
type AddressType @default(DELIVERY_RESIDENCE)
street String
houseNumber String
postalCode String
city String
country String @default("Deutschland")
isDefault Boolean @default(false)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== BANK CARDS ====================
model BankCard {
id Int @id @default(autoincrement())
customerId Int
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
accountHolder String
iban String
bic String?
bankName String?
expiryDate DateTime?
documentPath String? // Pfad zur hochgeladenen PDF
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== IDENTITY DOCUMENTS ====================
enum DocumentType {
ID_CARD
PASSPORT
DRIVERS_LICENSE
OTHER
}
model IdentityDocument {
id Int @id @default(autoincrement())
customerId Int
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
type DocumentType @default(ID_CARD)
documentNumber String
issuingAuthority String?
issueDate DateTime?
expiryDate DateTime?
documentPath String? // Pfad zur hochgeladenen PDF
isActive Boolean @default(true)
// Führerschein-spezifische Felder
licenseClasses String? // z.B. "B, BE, AM, L" - kommasepariert
licenseIssueDate DateTime? // Datum des Führerscheinerwerbs (Klasse B)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== EMAIL PROVIDER CONFIG (Plesk, cPanel etc.) ====================
enum EmailProviderType {
PLESK
CPANEL
DIRECTADMIN
}
model EmailProviderConfig {
id Int @id @default(autoincrement())
name String @unique // z.B. "Plesk Hauptserver"
type EmailProviderType
apiUrl String // API-URL (z.B. https://server.de:8443)
apiKey String? // API-Key (verschlüsselt)
username String? // Benutzername für API
passwordEncrypted String? // Passwort (verschlüsselt)
domain String // Domain für E-Mails (z.B. stressfrei-wechseln.de)
defaultForwardEmail String? // Standard-Weiterleitungsadresse (unsere eigene)
isActive Boolean @default(true)
isDefault Boolean @default(false) // Standard-Provider
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== STRESSFREI-WECHSELN EMAIL ADDRESSES ====================
model StressfreiEmail {
id Int @id @default(autoincrement())
customerId Int
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
email String // Die Weiterleitungs-E-Mail-Adresse
platform String? // Für welche Plattform (z.B. "Freenet", "Klarmobil")
notes String? @db.Text // Optionale Notizen
isActive Boolean @default(true)
isProvisioned Boolean @default(false) // Wurde bei Provider angelegt?
provisionedAt DateTime? // Wann wurde provisioniert?
provisionError String? @db.Text // Fehlermeldung falls Provisionierung fehlschlug
contracts Contract[] // Verträge die diese E-Mail als Benutzername verwenden
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== METERS (Energy) ====================
enum MeterType {
ELECTRICITY
GAS
}
model Meter {
id Int @id @default(autoincrement())
customerId Int
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
meterNumber String
type MeterType
location String?
isActive Boolean @default(true)
readings MeterReading[]
energyDetails EnergyContractDetails[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model MeterReading {
id Int @id @default(autoincrement())
meterId Int
meter Meter @relation(fields: [meterId], references: [id], onDelete: Cascade)
readingDate DateTime
value Float
unit String @default("kWh")
notes String?
createdAt DateTime @default(now())
}
// ==================== SALES PLATFORMS ====================
model SalesPlatform {
id Int @id @default(autoincrement())
name String @unique
contactInfo String? @db.Text
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== CANCELLATION PERIODS ====================
model CancellationPeriod {
id Int @id @default(autoincrement())
code String @unique // z.B. "14T", "1M", "3M", "12M", "1J"
description String // z.B. "14 Tage", "1 Monat", "3 Monate"
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== CONTRACT DURATIONS ====================
model ContractDuration {
id Int @id @default(autoincrement())
code String @unique // z.B. "12M", "24M", "1J", "2J"
description String // z.B. "12 Monate", "24 Monate", "1 Jahr"
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== PROVIDERS (Anbieter) ====================
model Provider {
id Int @id @default(autoincrement())
name String @unique // Anbietername
portalUrl String? // Kundenkontourl (Login-Seite)
usernameFieldName String? // Benutzernamefeld (z.B. "email", "username")
passwordFieldName String? // Kennwortfeld (z.B. "password", "pwd")
isActive Boolean @default(true)
tariffs Tariff[]
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== TARIFFS (Tarife) ====================
model Tariff {
id Int @id @default(autoincrement())
providerId Int
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
name String // Tarifname
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([providerId, name]) // Eindeutiger Tarif pro Anbieter
}
// ==================== CONTRACT CATEGORIES ====================
model ContractCategory {
id Int @id @default(autoincrement())
code String @unique // Technischer Code (z.B. ELECTRICITY, GAS)
name String // Anzeigename (z.B. Strom, Gas)
icon String? // Icon-Name für UI (z.B. "Zap", "Flame")
color String? // Farbe für UI (z.B. "#FFC107")
sortOrder Int @default(0)
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== CONTRACTS ====================
// Legacy Enum - wird durch ContractCategory ersetzt
enum ContractType {
ELECTRICITY
GAS
DSL
CABLE
FIBER
MOBILE
TV
CAR_INSURANCE
}
enum ContractStatus {
DRAFT
PENDING
ACTIVE
CANCELLED
EXPIRED
DEACTIVATED
}
model Contract {
id Int @id @default(autoincrement())
contractNumber String @unique
customerId Int
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
type ContractType
status ContractStatus @default(DRAFT)
// Neue konfigurierbare Kategorie (ersetzt langfristig das type-Enum)
contractCategoryId Int?
contractCategory ContractCategory? @relation(fields: [contractCategoryId], references: [id])
addressId Int?
address Address? @relation(fields: [addressId], references: [id])
bankCardId Int?
bankCard BankCard? @relation(fields: [bankCardId], references: [id])
identityDocumentId Int?
identityDocument IdentityDocument? @relation(fields: [identityDocumentId], references: [id])
salesPlatformId Int?
salesPlatform SalesPlatform? @relation(fields: [salesPlatformId], references: [id])
cancellationPeriodId Int?
cancellationPeriod CancellationPeriod? @relation(fields: [cancellationPeriodId], references: [id])
contractDurationId Int?
contractDuration ContractDuration? @relation(fields: [contractDurationId], references: [id])
previousContractId Int? @unique
previousContract Contract? @relation("ContractHistory", fields: [previousContractId], references: [id])
followUpContract Contract? @relation("ContractHistory")
// Anbieter & Tarif (neue Verknüpfung)
providerId Int?
provider Provider? @relation(fields: [providerId], references: [id])
tariffId Int?
tariff Tariff? @relation(fields: [tariffId], references: [id])
// Legacy-Felder (für Abwärtskompatibilität)
providerName String?
tariffName String?
customerNumberAtProvider String?
priceFirst12Months String? // Preis erste 12 Monate
priceFrom13Months String? // Preis ab 13. Monat
priceAfter24Months String? // Preis nach 24 Monaten
startDate DateTime?
endDate DateTime? // Wird aus startDate + contractDuration berechnet
commission Float?
// Kündigungsdokumente
cancellationLetterPath String? // Kündigungsschreiben PDF
cancellationConfirmationPath String? // Kündigungsbestätigung PDF
cancellationLetterOptionsPath String? // Kündigungsschreiben Optionen PDF
cancellationConfirmationOptionsPath String? // Kündigungsbestätigung Optionen PDF
// Kündigungsdaten
cancellationConfirmationDate DateTime? // Kündigungsbestätigungsdatum
cancellationConfirmationOptionsDate DateTime? // Kündigungsbestätigungsoptionendatum
wasSpecialCancellation Boolean @default(false) // Wurde sondergekündigt?
portalUsername String?
portalPasswordEncrypted String?
// Stressfrei-Wechseln E-Mail als Benutzername (Alternative zu portalUsername)
stressfreiEmailId Int?
stressfreiEmail StressfreiEmail? @relation(fields: [stressfreiEmailId], references: [id])
notes String? @db.Text
energyDetails EnergyContractDetails?
internetDetails InternetContractDetails?
mobileDetails MobileContractDetails?
tvDetails TvContractDetails?
carInsuranceDetails CarInsuranceDetails?
tasks ContractTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== CONTRACT TASKS ====================
enum ContractTaskStatus {
OPEN
COMPLETED
}
model ContractTask {
id Int @id @default(autoincrement())
contractId Int
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
title String
description String? @db.Text
status ContractTaskStatus @default(OPEN)
visibleInPortal Boolean @default(false)
createdBy String? // Name des Erstellers
completedAt DateTime?
subtasks ContractTaskSubtask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ContractTaskSubtask {
id Int @id @default(autoincrement())
taskId Int
task ContractTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
title String
status ContractTaskStatus @default(OPEN)
createdBy String?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== ENERGY CONTRACT DETAILS ====================
model EnergyContractDetails {
id Int @id @default(autoincrement())
contractId Int @unique
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
meterId Int?
meter Meter? @relation(fields: [meterId], references: [id])
annualConsumption Float?
basePrice Float?
unitPrice Float?
bonus Float?
previousProviderName String?
previousCustomerNumber String?
}
// ==================== INTERNET CONTRACT DETAILS ====================
model InternetContractDetails {
id Int @id @default(autoincrement())
contractId Int @unique
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
downloadSpeed Int?
uploadSpeed Int?
routerModel String?
routerSerialNumber String?
installationDate DateTime?
// Internet-Zugangsdaten
internetUsername String?
internetPasswordEncrypted String? // Verschlüsselt gespeichert
// Glasfaser-spezifisch
homeId String?
// Vodafone DSL/Kabel spezifisch
activationCode String?
phoneNumbers PhoneNumber[]
}
model PhoneNumber {
id Int @id @default(autoincrement())
internetContractDetailsId Int
internetDetails InternetContractDetails @relation(fields: [internetContractDetailsId], references: [id], onDelete: Cascade)
phoneNumber String
isMain Boolean @default(false)
// SIP-Zugangsdaten
sipUsername String?
sipPasswordEncrypted String? // Verschlüsselt gespeichert
sipServer String?
}
// ==================== MOBILE CONTRACT DETAILS ====================
model MobileContractDetails {
id Int @id @default(autoincrement())
contractId Int @unique
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
requiresMultisim Boolean @default(false) // Multisim erforderlich?
dataVolume Float?
includedMinutes Int?
includedSMS Int?
deviceModel String?
deviceImei String?
simCards SimCard[]
// Legacy-Felder (für Abwärtskompatibilität, werden durch simCards ersetzt)
phoneNumber String?
simCardNumber String?
}
model SimCard {
id Int @id @default(autoincrement())
mobileDetailsId Int
mobileDetails MobileContractDetails @relation(fields: [mobileDetailsId], references: [id], onDelete: Cascade)
phoneNumber String? // Rufnummer
simCardNumber String? // SIM-Kartennummer
pin String? // PIN (verschlüsselt gespeichert)
puk String? // PUK (verschlüsselt gespeichert)
isMultisim Boolean @default(false) // Ist dies eine Multisim-Karte?
isMain Boolean @default(false) // Ist dies die Hauptkarte?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ==================== TV CONTRACT DETAILS ====================
model TvContractDetails {
id Int @id @default(autoincrement())
contractId Int @unique
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
receiverModel String?
smartcardNumber String?
package String?
}
// ==================== CAR INSURANCE DETAILS ====================
enum InsuranceType {
LIABILITY
PARTIAL
FULL
}
model CarInsuranceDetails {
id Int @id @default(autoincrement())
contractId Int @unique
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
licensePlate String?
hsn String?
tsn String?
vin String?
vehicleType String?
firstRegistration DateTime?
noClaimsClass String?
insuranceType InsuranceType @default(LIABILITY)
deductiblePartial Float?
deductibleFull Float?
policyNumber String?
previousInsurer String?
}