generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum UserRole { ADMIN DRIVER } enum BookingStatus { RESERVED // Initial (nach E-Mail-Eingang) CONFIRMED // Admin hat bestätigt & gesendet READY_FOR_ASSIGNMENT // Vertrag unterschrieben → Admin-Freigabe OPEN_FOR_DRIVERS // Admin hat freigegeben → Fahrer können sich melden ASSIGNED // Admin hat Fahrer zugewiesen & Tour erstellt COMPLETED // Event abgeschlossen CANCELLED // Storniert } enum PhotoboxStatus { AVAILABLE IN_USE MAINTENANCE DAMAGED } enum PhotoboxModel { VINTAGE_SMILE VINTAGE_PHOTOS NOSTALGIE MAGIC_MIRROR VINTAGE } enum InvoiceType { PRIVATE BUSINESS } enum EquipmentType { PRINTER CARPET VIP_BARRIER ACCESSORIES_KIT PRINTER_PAPER TRIPOD OTHER } enum EquipmentStatus { AVAILABLE IN_USE MAINTENANCE DAMAGED RESERVED } model User { id String @id @default(cuid()) email String @unique name String password String role UserRole @default(DRIVER) phoneNumber String? vehiclePlate String? vehicleModel String? active Boolean @default(true) available Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt driverTours Tour[] driverLocations DriverLocation[] notifications Notification[] driverAvailability DriverAvailability[] } model Location { id String @id @default(cuid()) name String city String slug String @unique websiteUrl String contactEmail String active Boolean @default(true) warehouseAddress String? warehouseZip String? warehouseCity String? imapHost String? imapPort Int? imapUser String? imapPassword String? imapSecure Boolean @default(true) smtpHost String? smtpPort Int? smtpUser String? smtpPassword String? smtpSecure Boolean @default(true) emailSyncEnabled Boolean @default(false) lastEmailSync DateTime? priceConfig PriceConfig[] photoboxes Photobox[] bookings Booking[] equipment Equipment[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([slug]) } model PriceConfig { id String @id @default(cuid()) locationId String location Location @relation(fields: [locationId], references: [id], onDelete: Cascade) model PhotoboxModel basePrice Float kmFlatRate Float @default(0) kmFlatRateUpTo Int @default(15) pricePerKm Float @default(0) kmMultiplier Int @default(4) lexofficeArticleId String? lexofficeArticleIdWithFlat String? lexofficeKmFlatArticleId String? lexofficeKmExtraArticleId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([locationId, model]) } model Photobox { id String @id @default(cuid()) locationId String location Location @relation(fields: [locationId], references: [id], onDelete: Cascade) model PhotoboxModel serialNumber String @unique status PhotoboxStatus @default(AVAILABLE) active Boolean @default(true) description String? purchaseDate DateTime? lastMaintenance DateTime? bookings Booking[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([locationId, model]) @@index([status]) } model Booking { id String @id @default(cuid()) bookingNumber String @unique locationId String location Location @relation(fields: [locationId], references: [id]) photoboxId String? photobox Photobox? @relation(fields: [photoboxId], references: [id]) status BookingStatus @default(RESERVED) customerName String customerEmail String customerPhone String customerAddress String? customerCity String? customerZip String? invoiceType InvoiceType @default(PRIVATE) companyName String? eventDate DateTime eventAddress String eventCity String eventZip String eventLocation String? setupTimeStart DateTime setupTimeLatest DateTime dismantleTimeEarliest DateTime? dismantleTimeLatest DateTime? withPrintFlat Boolean @default(true) distance Float? calculatedPrice Float? // Contract Management contractSigned Boolean @default(false) contractSignedAt DateTime? contractGenerated Boolean @default(false) contractGeneratedAt DateTime? contractSentAt DateTime? contractSignedOnline Boolean @default(false) contractPdfUrl String? contractSignatureData String? @db.Text contractSignedBy String? contractSignedIp String? contractUploadedBy String? lexofficeOfferId String? lexofficeInvoiceId String? lexofficeContactId String? lexofficeConfirmationId String? confirmationSentAt DateTime? // KI-Analyse aiParsed Boolean @default(false) aiResponseDraft String? @db.Text aiProcessedAt DateTime? // Freigabe-Status readyForAssignment Boolean @default(false) openForDrivers Boolean @default(false) // Kalender-Sync (Nextcloud) calendarEventId String? calendarSynced Boolean @default(false) calendarSyncedAt DateTime? tourId String? tour Tour? @relation(fields: [tourId], references: [id]) tourStops TourStop[] notes String? internalNotes String? emails Email[] bookingEquipment BookingEquipment[] driverAvailability DriverAvailability[] setupWindows SetupWindow[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([eventDate]) @@index([status]) @@index([locationId]) } model SetupWindow { id String @id @default(cuid()) bookingId String booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade) setupDate DateTime setupTimeStart DateTime setupTimeEnd DateTime preferred Boolean @default(false) selected Boolean @default(false) notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([bookingId]) @@index([setupDate]) } enum TourStatus { PLANNED IN_PROGRESS COMPLETED CANCELLED } enum StopStatus { PENDING // Noch nicht erreicht EN_ROUTE // Fahrer ist unterwegs ARRIVED // Fahrer ist angekommen SETUP_STARTED // Aufbau begonnen SETUP_COMPLETE // Aufbau abgeschlossen PICKUP_STARTED // Abbau/Abholung begonnen PICKUP_COMPLETE // Abholung abgeschlossen SKIPPED // Übersprungen ISSUE // Problem aufgetreten } enum PhotoType { SETUP_BEFORE // Vor dem Aufbau SETUP_AFTER // Nach dem Aufbau PICKUP_BEFORE // Vor dem Abbau PICKUP_AFTER // Nach dem Abbau ISSUE // Problem-Dokumentation OTHER // Sonstiges } model Tour { id String @id @default(cuid()) tourDate DateTime tourNumber String @unique driverId String? driver User? @relation(fields: [driverId], references: [id]) bookings Booking[] tourStops TourStop[] routeOptimized Json? totalDistance Float? estimatedDuration Int? status TourStatus @default(PLANNED) startedAt DateTime? completedAt DateTime? notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([tourDate]) @@index([driverId]) @@index([status]) } model TourStop { id String @id @default(cuid()) tourId String tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) bookingId String booking Booking @relation(fields: [bookingId], references: [id]) stopOrder Int // Reihenfolge der Stopps (1, 2, 3, ...) stopType String @default("DELIVERY") // DELIVERY, PICKUP, BOTH status StopStatus @default(PENDING) // Timestamps für jeden Status arrivedAt DateTime? setupStartedAt DateTime? setupCompleteAt DateTime? pickupStartedAt DateTime? pickupCompleteAt DateTime? // Optional: GPS-Position bei Ankunft arrivalLatitude Float? arrivalLongitude Float? // Notizen vom Fahrer notes String? @db.Text issueDescription String? @db.Text photos DeliveryPhoto[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([tourId, bookingId]) @@index([tourId, stopOrder]) @@index([status]) } model DriverLocation { id String @id @default(cuid()) driverId String driver User @relation(fields: [driverId], references: [id], onDelete: Cascade) latitude Float longitude Float accuracy Float? // GPS-Genauigkeit in Metern heading Float? // Richtung in Grad (0-360) speed Float? // Geschwindigkeit in km/h tourId String? // Optional: Welcher Stopp ist aktuell? currentStopId String? createdAt DateTime @default(now()) @@index([driverId, createdAt]) @@index([tourId]) } model DeliveryPhoto { id String @id @default(cuid()) tourStopId String tourStop TourStop @relation(fields: [tourStopId], references: [id], onDelete: Cascade) photoType PhotoType // Dateispeicherung (z.B. /uploads/photos/...) fileName String filePath String fileSize Int? mimeType String? // Optional: GPS-Position wo das Foto aufgenommen wurde latitude Float? longitude Float? caption String? @db.Text uploadedAt DateTime @default(now()) @@index([tourStopId]) @@index([photoType]) } model Notification { id String @id @default(cuid()) userId String? user User? @relation(fields: [userId], references: [id]) type String title String message String read Boolean @default(false) metadata Json? createdAt DateTime @default(now()) @@index([userId, read]) } model Email { id String @id @default(cuid()) locationSlug String? from String to String subject String textBody String? htmlBody String? messageId String? @unique inReplyTo String? bookingId String? booking Booking? @relation(fields: [bookingId], references: [id]) parsed Boolean @default(false) parsedData Json? direction String @default("INBOUND") receivedAt DateTime @default(now()) createdAt DateTime @default(now()) @@index([locationSlug]) @@index([bookingId]) @@index([receivedAt]) } model Project { id String @id @default(cuid()) name String description String? active Boolean @default(true) equipment Equipment[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Equipment { id String @id @default(cuid()) name String type EquipmentType brand String? model String? serialNumber String? @unique quantity Int @default(1) status EquipmentStatus @default(AVAILABLE) locationId String? location Location? @relation(fields: [locationId], references: [id]) projectId String? project Project? @relation(fields: [projectId], references: [id]) notes String? purchaseDate DateTime? purchasePrice Decimal? price Float? lexofficeArticleId String? minStockLevel Int? currentStock Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt bookingEquipment BookingEquipment[] @@index([type]) @@index([status]) @@index([locationId]) } model BookingEquipment { id String @id @default(cuid()) bookingId String booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade) equipmentId String equipment Equipment @relation(fields: [equipmentId], references: [id]) quantity Int @default(1) createdAt DateTime @default(now()) @@unique([bookingId, equipmentId]) @@index([bookingId]) @@index([equipmentId]) } model DriverAvailability { id String @id @default(cuid()) bookingId String booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade) driverId String driver User @relation(fields: [driverId], references: [id], onDelete: Cascade) available Boolean @default(true) message String? @db.Text createdAt DateTime @default(now()) @@unique([bookingId, driverId]) @@index([bookingId]) @@index([driverId]) }