- Vintage Modell hinzugefuegt - Equipment Multi-Select (Neue Buchung + Bearbeitung) - Kundenadresse in Formularen - Bearbeiten-Seite fuer Buchungen - Abbau-Zeiten in Formularen und Uebersicht - Vertrag PDF nur bei Privatkunden - LexOffice Kontakt-Erstellung Fix (BUSINESS) - Zurueck-Pfeil auf Touren-Seite
554 lines
14 KiB
Plaintext
554 lines
14 KiB
Plaintext
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])
|
|
}
|