Files
carmanagement/schema.md
T
Administrator 65394f6436 project design
2026-04-30 18:28:05 +00:00

35 KiB

Prisma Schema — RentFlow (Complete)

Stripe has been removed. Payments use AmanPay and PayPal.

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// ═══════════════════════════════════════════════════════════════
// B2B — COMPANY SIDE
// ═══════════════════════════════════════════════════════════════

model Company {
  id                    String          @id @default(cuid())
  name                  String
  slug                  String          @unique
  email                 String          @unique
  phone                 String?
  address               Json?
  status                CompanyStatus   @default(PENDING)

  // RentFlow billing — company pays subscription via AmanPay or PayPal
  // No third-party customer ID needed — we manage payment references ourselves
  subscriptionPaymentRef  String?       // last AmanPay/PayPal transaction ref

  // Public API key
  apiKey                String          @unique @default(cuid())

  subscription          Subscription?
  brand                 BrandSettings?
  employees             Employee[]
  vehicles              Vehicle[]
  offers                Offer[]
  reservations          Reservation[]
  customers             Customer[]
  rentalPayments        RentalPayment[]
  subscriptionInvoices  SubscriptionInvoice[]
  contractSettings      ContractSettings?
  accountingSettings    AccountingSettings?
  notifications         Notification[]  @relation("CompanyNotifications")

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("companies")
}

enum CompanyStatus {
  PENDING
  TRIALING
  ACTIVE
  PAST_DUE
  SUSPENDED
  CANCELLED
}

// ─── Subscription ─────────────────────────────────────────────

model Subscription {
  id                    String              @id @default(cuid())
  companyId             String              @unique
  company               Company             @relation(fields: [companyId], references: [id], onDelete: Cascade)

  plan                  Plan                @default(STARTER)
  billingPeriod         BillingPeriod       @default(MONTHLY)
  status                SubscriptionStatus  @default(TRIALING)
  currency              String              @default("MAD")  // MAD, USD, EUR

  trialStartAt          DateTime?
  trialEndAt            DateTime?
  currentPeriodStart    DateTime?
  currentPeriodEnd      DateTime?
  cancelledAt           DateTime?
  cancelAtPeriodEnd     Boolean             @default(false)

  invoices              SubscriptionInvoice[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("subscriptions")
}

enum Plan           { STARTER  GROWTH  PRO }
enum BillingPeriod  { MONTHLY  ANNUAL }
enum SubscriptionStatus { TRIALING  ACTIVE  PAST_DUE  CANCELLED  UNPAID }

// ─── Subscription Invoice ──────────────────────────────────────
// Records each subscription payment (AmanPay or PayPal)

model SubscriptionInvoice {
  id                    String          @id @default(cuid())
  companyId             String
  company               Company         @relation(fields: [companyId], references: [id], onDelete: Cascade)
  subscriptionId        String
  subscription          Subscription    @relation(fields: [subscriptionId], references: [id])

  amount                Int             // in smallest unit (centimes for MAD)
  currency              String          @default("MAD")
  status                InvoiceStatus

  // Payment provider reference — exactly one is set
  amanpayTransactionId  String?         @unique
  paypalCaptureId       String?         @unique
  paymentProvider       PaymentProvider @default(AMANPAY)

  paidAt                DateTime?

  createdAt DateTime @default(now())

  @@index([companyId])
  @@map("subscription_invoices")
}

enum InvoiceStatus   { PENDING  PAID  FAILED  REFUNDED }
enum FuelPolicyType  { FULL_TO_FULL  FULL_TO_EMPTY  SAME_TO_SAME  PREPAID  FREE }
enum PaymentProvider { AMANPAY  PAYPAL }

// ─── Brand Settings ───────────────────────────────────────────

model BrandSettings {
  id                    String    @id @default(cuid())
  companyId             String    @unique
  company               Company   @relation(fields: [companyId], references: [id], onDelete: Cascade)

  displayName           String
  tagline               String?
  logoUrl               String?
  faviconUrl            String?
  heroImageUrl          String?

  primaryColor          String    @default("#1A56DB")
  accentColor           String    @default("#F59E0B")

  subdomain             String    @unique
  customDomain          String?   @unique
  customDomainVerified  Boolean   @default(false)
  customDomainAddedAt   DateTime?

  // Public contact info
  publicEmail           String?
  publicPhone           String?
  publicAddress         String?
  publicCity            String?
  publicCountry         String?
  websiteUrl            String?
  whatsappNumber        String?
  facebookUrl           String?
  instagramUrl          String?

  // Locale + currency for public site
  defaultLocale         String    @default("en")
  defaultCurrency       String    @default("MAD")

  // Company's own payment accounts (for receiving rental payments)
  amanpayMerchantId     String?   // company's AmanPay merchant ID
  amanpaySecretKey      String?   // encrypted at rest
  paypalEmail           String?   // company's PayPal business email
  paypalMerchantId      String?   // optional PayPal merchant ID

  paymentMethodsEnabled PaymentProvider[]   // which methods they have set up

  // Marketplace
  isListedOnMarketplace Boolean   @default(true)
  marketplaceRating     Float?

  updatedAt DateTime @updatedAt

  @@map("brand_settings")
}

// ─── Employee ─────────────────────────────────────────────────

model Employee {
  id            String       @id @default(cuid())
  companyId     String
  company       Company      @relation(fields: [companyId], references: [id], onDelete: Cascade)

  clerkUserId   String       @unique
  firstName     String
  lastName      String
  email         String
  phone         String?
  role          EmployeeRole @default(AGENT)
  isActive      Boolean      @default(true)

  notifications             Notification[]           @relation("EmployeeNotifications")
  notificationPreferences   NotificationPreference[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@map("employees")
}

enum EmployeeRole { OWNER  MANAGER  AGENT }

// ─── Vehicle ──────────────────────────────────────────────────
// Strictly company-scoped. Photos uploaded by the company appear
// on their public site AND on the global marketplace.

model Vehicle {
  id            String          @id @default(cuid())
  companyId     String
  company       Company         @relation(fields: [companyId], references: [id], onDelete: Cascade)

  make          String
  model         String
  year          Int
  color         String
  licensePlate  String
  vin           String?
  category      VehicleCategory @default(ECONOMY)
  seats         Int             @default(5)
  transmission  Transmission    @default(AUTOMATIC)
  fuelType      FuelType        @default(GASOLINE)
  features      String[]        // ["AC", "GPS", "Bluetooth", "Baby seat"]

  dailyRate     Int             // in smallest currency unit
  status        VehicleStatus   @default(AVAILABLE)

  // Photos uploaded via Cloudinary — these URLs are shown on the marketplace
  photos        String[]        // Cloudinary HTTPS URLs, first photo is the "cover"

  mileage       Int?
  notes         String?
  isPublished   Boolean         @default(true)  // appears on public site + marketplace

  reservations  Reservation[]
  maintenance   MaintenanceLog[]
  offerVehicles OfferVehicle[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@index([companyId, status])
  @@index([companyId, isPublished])
  @@map("vehicles")
}

enum VehicleStatus   { AVAILABLE  RENTED  MAINTENANCE  OUT_OF_SERVICE }
enum VehicleCategory { ECONOMY  COMPACT  MIDSIZE  FULLSIZE  SUV  LUXURY  VAN  TRUCK }
enum Transmission    { AUTOMATIC  MANUAL }
enum FuelType        { GASOLINE  DIESEL  ELECTRIC  HYBRID }

// ─── Offer ────────────────────────────────────────────────────

model Offer {
  id              String      @id @default(cuid())
  companyId       String
  company         Company     @relation(fields: [companyId], references: [id], onDelete: Cascade)

  title           String
  description     String?
  termsAndConds   String?
  imageUrl        String?     // offer banner (Cloudinary)

  type            OfferType
  discountValue   Int
  specialRate     Int?

  appliesToAll    Boolean     @default(true)
  categories      VehicleCategory[]
  minRentalDays   Int?
  maxRentalDays   Int?

  promoCode       String?     @unique
  maxRedemptions  Int?
  redemptionCount Int         @default(0)

  validFrom       DateTime
  validUntil      DateTime
  isActive        Boolean     @default(true)
  isPublic        Boolean     @default(true)   // shown on global marketplace
  isFeatured      Boolean     @default(false)  // featured carousel (GROWTH+ plan)

  vehicles        OfferVehicle[]
  reservations    Reservation[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@index([isPublic, isActive, validUntil])
  @@map("offers")
}

enum OfferType { PERCENTAGE  FIXED_AMOUNT  FREE_DAY  SPECIAL_RATE }

model OfferVehicle {
  offerId   String
  offer     Offer   @relation(fields: [offerId], references: [id], onDelete: Cascade)
  vehicleId String
  vehicle   Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)

  @@id([offerId, vehicleId])
  @@map("offer_vehicles")
}

// ═══════════════════════════════════════════════════════════════
// B2C — RENTER SIDE
// ═══════════════════════════════════════════════════════════════

model Renter {
  id                String    @id @default(cuid())
  firstName         String
  lastName          String
  email             String    @unique
  phone             String?
  passwordHash      String
  driverLicense     String?
  dateOfBirth       DateTime?
  nationality       String?
  preferredLocale   String    @default("en")
  preferredCurrency String    @default("MAD")
  fcmToken          String?

  emailVerified     Boolean   @default(false)
  phoneVerified     Boolean   @default(false)
  isActive          Boolean   @default(true)

  reservations              Reservation[]           @relation("RenterReservations")
  savedCompanies            RenterSavedCompany[]
  reviews                   Review[]
  notifications             Notification[]          @relation("RenterNotifications")
  notificationPreferences   NotificationPreference[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("renters")
}

model RenterSavedCompany {
  renterId  String
  renter    Renter  @relation(fields: [renterId], references: [id], onDelete: Cascade)
  companyId String
  savedAt   DateTime @default(now())

  @@id([renterId, companyId])
  @@map("renter_saved_companies")
}

// Customer = company-scoped CRM record. Auto-created when someone books with a company.
model Customer {
  id            String    @id @default(cuid())
  companyId     String
  company       Company   @relation(fields: [companyId], references: [id], onDelete: Cascade)
  renterId      String?   // linked to global Renter (if booked as logged-in renter)

  firstName     String
  lastName      String
  email         String
  phone         String?
  driverLicense String?
  dateOfBirth   DateTime?
  nationality   String?
  address       Json?
  notes         String?
  flagged       Boolean   @default(false)
  flagReason    String?

  // ── Driver license details & validation ───────────────────
  licenseExpiry            DateTime?
  licenseIssuedAt          DateTime?
  licenseCountry           String?
  licenseNumber            String?
  licenseCategory          String?   // "B", "A", "BE", etc.
  licenseExpired           Boolean   @default(false)
  licenseExpiringSoon      Boolean   @default(false)   // <3 months validity
  licenseValidationStatus  LicenseStatus @default(PENDING)
  licenseApprovedBy        String?   // Employee name
  licenseApprovedAt        DateTime?
  licenseApprovalNote      String?

  reservations  Reservation[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@unique([companyId, email])
  @@index([companyId])
  @@map("customers")
}

// ─── Reservation ──────────────────────────────────────────────

model Reservation {
  id              String            @id @default(cuid())
  companyId       String
  company         Company           @relation(fields: [companyId], references: [id], onDelete: Cascade)

  vehicleId       String
  vehicle         Vehicle           @relation(fields: [vehicleId], references: [id])

  customerId      String
  customer        Customer          @relation(fields: [customerId], references: [id])

  renterId        String?           // global renter account
  renter          Renter?           @relation("RenterReservations", fields: [renterId], references: [id])

  offerId         String?
  offer           Offer?            @relation(fields: [offerId], references: [id])
  promoCodeUsed   String?

  // Where the booking originated
  source          BookingSource     @default(DASHBOARD)
  // For marketplace bookings: renter selected vehicle on explore, was redirected here
  marketplaceRef  String?           // vehicle ID + dates encoded from marketplace redirect

  status          ReservationStatus @default(DRAFT)

  startDate       DateTime
  endDate         DateTime
  pickupLocation  String?
  returnLocation  String?

  dailyRate       Int
  discountAmount  Int               @default(0)
  totalDays       Int
  totalAmount     Int
  depositAmount   Int               @default(0)

  checkedInAt     DateTime?
  checkedOutAt    DateTime?
  checkInMileage  Int?
  checkOutMileage Int?

  notes           String?
  cancelReason    String?
  cancelledBy     CancelledBy?

  // Document reference numbers — assigned once, stored permanently
  // PDFs are NEVER stored — generated fresh from DB data on every request
  contractNumber    String?   @unique   // e.g. "CNT-2024-00042"
  invoiceNumber     String?   @unique   // e.g. "INV-2024-00042"

  // Additional check-in data needed for contract
  // check-in fuel moved to DamageReport.fuelLevel — kept for backward compat
  checkInFuelLevel  String?
  checkOutFuelLevel String?

  // ── Insurance, additional driver, pricing, damage ─────────
  insurances            ReservationInsurance[]
  insuranceTotal        Int        @default(0)
  additionalDrivers     AdditionalDriver[]
  additionalDriverTotal Int        @default(0)
  pricingRulesApplied   Json?      // [{ruleId, name, type, amount}] snapshot at booking
  pricingRulesTotal     Int        @default(0)
  damageReports         DamageReport[]

  rentalPayments  RentalPayment[]
  inspections     DamageInspection[]
  review          Review?

  // Damage charge (added by employee after checkout inspection)
  damageChargeAmount  Int?
  damageChargeNote    String?

  // Extras (GPS, baby seat, insurance, extra driver, etc.) — JSON array
  extras          Json?    // ReservationExtra[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@index([companyId, status])
  @@index([vehicleId, startDate, endDate])
  @@index([renterId])
  @@map("reservations")
}

enum ReservationStatus { DRAFT  CONFIRMED  ACTIVE  COMPLETED  CANCELLED  NO_SHOW }
enum BookingSource     { DASHBOARD  PUBLIC_SITE  MARKETPLACE  API }
enum CancelledBy       { COMPANY  RENTER  SYSTEM }
enum LicenseStatus     { PENDING  VALID  EXPIRING  APPROVED  DENIED  EXPIRED }
enum InsuranceType     { CDW  SCDW  THEFT  THIRD_PARTY  FULL  BASIC  ROADSIDE  PERSONAL  CUSTOM }
enum InsuranceChargeType { PER_DAY  PER_RENTAL  PERCENTAGE_OF_RENTAL }
enum DamageReportType  { CHECKIN  CHECKOUT }
enum PricingRuleType   { SURCHARGE  DISCOUNT }
enum PricingCondition  { AGE_LESS_THAN  AGE_GREATER_THAN  LICENSE_YEARS_LESS_THAN  LICENSE_YEARS_GREATER_THAN }
enum PriceAdjustmentType { PERCENTAGE  FLAT_PER_DAY  FLAT_TOTAL }
enum AdditionalDriverCharge { FREE  PER_DAY  FLAT }
enum AdminRole          { SUPER_ADMIN  ADMIN  SUPPORT  FINANCE  VIEWER }

// ─── Rental Payment ───────────────────────────────────────────
// Payment from renter to company. No Stripe — uses AmanPay or PayPal.

model RentalPayment {
  id                    String          @id @default(cuid())
  companyId             String
  company               Company         @relation(fields: [companyId], references: [id], onDelete: Cascade)
  reservationId         String
  reservation           Reservation     @relation(fields: [reservationId], references: [id])

  amount                Int             // in smallest unit
  currency              String          @default("MAD")
  status                PaymentStatus   @default(PENDING)
  type                  PaymentType     @default(CHARGE)
  paymentProvider       PaymentProvider

  // Exactly one of these is set:
  amanpayTransactionId  String?         @unique
  paypalCaptureId       String?         @unique

  paymentMethod         String?         // "card_national", "card_international", "cash", "ewallet", "paypal"
  paidAt                DateTime?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@index([reservationId])
  @@map("rental_payments")
}

enum PaymentStatus { PENDING  SUCCEEDED  FAILED  REFUNDED  PARTIALLY_REFUNDED }
enum PaymentType   { CHARGE  DEPOSIT  REFUND }

// ─── Review ───────────────────────────────────────────────────

model Review {
  id              String      @id @default(cuid())
  reservationId   String      @unique
  reservation     Reservation @relation(fields: [reservationId], references: [id])
  renterId        String
  renter          Renter      @relation(fields: [renterId], references: [id])
  companyId       String

  overallRating   Int
  vehicleRating   Int?
  serviceRating   Int?
  comment         String?
  isPublished     Boolean     @default(true)
  companyReply    String?
  companyRepliedAt DateTime?

  createdAt DateTime @default(now())

  @@index([companyId])
  @@map("reviews")
}

// ═══════════════════════════════════════════════════════════════
// NOTIFICATIONS
// ═══════════════════════════════════════════════════════════════

model Notification {
  id          String               @id @default(cuid())

  companyId   String?
  company     Company?             @relation("CompanyNotifications", fields: [companyId], references: [id], onDelete: Cascade)
  employeeId  String?
  employee    Employee?            @relation("EmployeeNotifications", fields: [employeeId], references: [id], onDelete: Cascade)
  renterId    String?
  renter      Renter?              @relation("RenterNotifications", fields: [renterId], references: [id], onDelete: Cascade)

  type        NotificationType
  title       String
  body        String
  data        Json?

  channel     NotificationChannel
  status      NotificationStatus  @default(PENDING)
  sentAt      DateTime?
  failReason  String?
  readAt      DateTime?
  providerMessageId String?

  createdAt DateTime @default(now())

  @@index([companyId])
  @@index([renterId])
  @@map("notifications")
}

enum NotificationType {
  NEW_BOOKING  BOOKING_CANCELLED  PAYMENT_RECEIVED  PAYMENT_FAILED
  SUBSCRIPTION_TRIAL_ENDING  SUBSCRIPTION_SUSPENDED
  VEHICLE_MAINTENANCE_DUE  OFFER_EXPIRING  NEW_REVIEW_RECEIVED
  BOOKING_CONFIRMED  PICKUP_REMINDER_24H  PICKUP_REMINDER_2H
  VEHICLE_READY  RETURN_REMINDER  BOOKING_CANCELLED_BY_COMPANY
  REFUND_PROCESSED  NEW_OFFER_FROM_SAVED_COMPANY  REVIEW_REQUEST
}

enum NotificationChannel { EMAIL  SMS  WHATSAPP  IN_APP  PUSH }
enum NotificationStatus  { PENDING  SENT  DELIVERED  FAILED  READ }

model NotificationPreference {
  id                String              @id @default(cuid())
  employeeId        String?
  employee          Employee?           @relation(fields: [employeeId], references: [id], onDelete: Cascade)
  renterId          String?
  renter            Renter?             @relation(fields: [renterId], references: [id], onDelete: Cascade)
  notificationType  NotificationType
  channel           NotificationChannel
  enabled           Boolean             @default(true)

  @@unique([employeeId, notificationType, channel])
  @@unique([renterId, notificationType, channel])
  @@map("notification_preferences")
}

// ─── Maintenance Log ──────────────────────────────────────────

model MaintenanceLog {
  id              String    @id @default(cuid())
  vehicleId       String
  vehicle         Vehicle   @relation(fields: [vehicleId], references: [id], onDelete: Cascade)

  type            String
  description     String?
  cost            Int?
  mileage         Int?
  performedAt     DateTime
  nextDueAt       DateTime?
  nextDueMileage  Int?

  createdAt DateTime @default(now())

  @@index([vehicleId])
  @@map("maintenance_logs")
}

// ═══════════════════════════════════════════════════════════════
// DOCUMENTS — Rental Contracts & Customer Invoices
// ═══════════════════════════════════════════════════════════════
// PDFs are NEVER stored. All fields here are DATA, not files.
// contractNumber and invoiceNumber are assigned once and immutable.

// ─── Contract Settings ────────────────────────────────────────
// Per-company contract customization. One record per company.
// Auto-created with defaults if not yet configured.

model ContractSettings {
  id          String   @id @default(cuid())
  companyId   String   @unique
  company     Company  @relation(fields: [companyId], references: [id], onDelete: Cascade)

  // Legal identity (may differ from brand display name)
  legalName           String?
  registrationNumber  String?   // RC / SIRET / ICE
  taxId               String?   // TVA / ICE number

  // Contract text (company writes their own)
  terms               String    @default("")
  fuelPolicy          String    @default("The vehicle must be returned with the same fuel level as at pickup.")
  depositPolicy       String    @default("The security deposit is refundable within 7 days after return, subject to vehicle inspection.")
  lateFeePolicy       String    @default("Late returns will incur a charge of 1 additional day rate per hour of delay.")
  damagePolicy        String    @default("The renter is liable for any damage not covered by insurance.")
  additionalClauses   String[]  @default([])

  // Contract options
  signatureRequired   Boolean   @default(true)
  contractFooterNote  String?

  // Invoice options
  invoiceFooterNote   String?
  showTax             Boolean   @default(false)
  taxRate             Float?    // 0.20 = 20%
  taxLabel            String?   // "TVA" | "VAT" | "Tax"

  // Sequential numbering — incremented atomically via DB transaction
  contractPrefix      String    @default("CNT")   // CNT-2024-00001
  invoicePrefix       String    @default("INV")   // INV-2024-00001

  // Structured fuel policy
  fuelPolicyType      FuelPolicyType  @default(FULL_TO_FULL)
  fuelPolicyNote      String?
  fuelChargePerLiter  Int?            // in cents per liter
  fuelShortfallFee    Int?            // flat fee in cents if not returned full
  lateFeePerHour      Int?            // in cents per hour of late return

  // Multiple tax lines as JSON: [{ label, rate, appliesToCategories? }]
  taxLines            Json?
  lastContractSeq     Int       @default(0)       // NEVER reset via API
  lastInvoiceSeq      Int       @default(0)       // NEVER reset via API

  // Additional driver pricing
  additionalDriverCharge       AdditionalDriverCharge @default(FREE)
  additionalDriverDailyRate    Int     @default(0)   // in smallest unit
  additionalDriverFlatRate     Int     @default(0)

  updatedAt DateTime @updatedAt

  @@map("contract_settings")
}

// ─── Insurance Policy ─────────────────────────────────────────

model InsurancePolicy {
  id          String   @id @default(cuid())
  companyId   String
  company     Company  @relation(fields: [companyId], references: [id], onDelete: Cascade)

  name        String
  description String?
  type        InsuranceType
  chargeType  InsuranceChargeType
  chargeValue Int      // per-day rate, flat fee, or percentage (0-100)
  isRequired  Boolean  @default(false)
  isActive    Boolean  @default(true)
  sortOrder   Int      @default(0)

  reservations ReservationInsurance[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@index([companyId, isActive])
  @@map("insurance_policies")
}

model ReservationInsurance {
  id                 String           @id @default(cuid())
  reservationId      String
  reservation        Reservation      @relation(fields: [reservationId], references: [id], onDelete: Cascade)
  insurancePolicyId  String
  insurancePolicy    InsurancePolicy  @relation(fields: [insurancePolicyId], references: [id])

  // Snapshot at booking time
  policyName         String
  policyType         InsuranceType
  chargeType         InsuranceChargeType
  chargeValue        Int
  totalCharge        Int

  @@unique([reservationId, insurancePolicyId])
  @@map("reservation_insurances")
}

// ─── Additional Driver ────────────────────────────────────────

model AdditionalDriver {
  id              String      @id @default(cuid())
  reservationId   String
  reservation     Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade)
  companyId       String

  firstName       String
  lastName        String
  email           String?
  phone           String?
  driverLicense   String
  licenseExpiry   DateTime?
  licenseIssuedAt DateTime?
  dateOfBirth     DateTime?
  nationality     String?

  chargeType      AdditionalDriverCharge @default(FREE)
  chargeValue     Int                    @default(0)
  totalCharge     Int                    @default(0)

  licenseExpired          Boolean   @default(false)
  licenseExpiringSoon     Boolean   @default(false)
  requiresApproval        Boolean   @default(false)
  approvedBy              String?
  approvedAt              DateTime?
  approvalNote            String?

  createdAt DateTime @default(now())

  @@index([reservationId])
  @@index([companyId])
  @@map("additional_drivers")
}

// ─── Damage Report ────────────────────────────────────────────

model DamageReport {
  id              String           @id @default(cuid())
  reservationId   String
  reservation     Reservation      @relation(fields: [reservationId], references: [id], onDelete: Cascade)
  companyId       String
  type            DamageReportType

  damages         Json             // DamageZone[] — see advanced-features.md
  photos          String[]         // Cloudinary URLs (optional damage photos)
  fuelLevel       FuelLevel        @default(FULL)
  mileage         Int?
  inspectedAt     DateTime         @default(now())
  inspectedBy     String?
  customerSignedAt DateTime?
  customerName    String?

  @@unique([reservationId, type])
  @@index([companyId])
  @@map("damage_reports")
}

// ─── Pricing Rule ─────────────────────────────────────────────

model PricingRule {
  id              String              @id @default(cuid())
  companyId       String
  company         Company             @relation(fields: [companyId], references: [id], onDelete: Cascade)

  name            String
  type            PricingRuleType
  condition       PricingCondition
  conditionValue  Int                 // age or years threshold
  adjustmentType  PriceAdjustmentType
  adjustmentValue Int                 // percentage (0-100) or flat amount in smallest unit
  isActive        Boolean             @default(true)
  description     String?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([companyId])
  @@map("pricing_rules")
}

// ═══════════════════════════════════════════════════════════════
// DAMAGE INSPECTION SYSTEM
// ═══════════════════════════════════════════════════════════════

model DamageInspection {
  id              String          @id @default(cuid())
  reservationId   String
  reservation     Reservation     @relation(fields: [reservationId], references: [id], onDelete: Cascade)
  companyId       String

  type            InspectionType
  mileage         Int?
  fuelLevel       FuelLevel
  fuelCharge      Int?            // in cents (calculated at checkout)
  generalCondition String?
  employeeNotes   String?
  customerAgreed  Boolean         @default(false)
  inspectedAt     DateTime        @default(now())
  inspectedBy     String?         // employeeId

  damagePoints    DamagePoint[]

  createdAt DateTime @default(now())

  @@unique([reservationId, type])
  @@index([reservationId])
  @@index([companyId])
  @@map("damage_inspections")
}

enum InspectionType { CHECKIN  CHECKOUT }

enum FuelLevel {
  FULL  SEVEN_EIGHTHS  THREE_QUARTERS  FIVE_EIGHTHS
  HALF  THREE_EIGHTHS  QUARTER  ONE_EIGHTH  EMPTY
}

model DamagePoint {
  id           String           @id @default(cuid())
  inspectionId String
  inspection   DamageInspection @relation(fields: [inspectionId], references: [id], onDelete: Cascade)

  viewType     DiagramView
  x            Float            // 0-100 percentage of diagram width
  y            Float            // 0-100 percentage of diagram height

  damageType   DamageType
  severity     DamageSeverity   @default(MINOR)
  description  String?
  isPreExisting Boolean         @default(false)

  @@index([inspectionId])
  @@map("damage_points")
}

enum DiagramView    { TOP  FRONT  REAR  LEFT  RIGHT }
enum DamageType     { SCRATCH  DENT  CRACK  CHIP  MISSING  STAIN  OTHER }
enum DamageSeverity { MINOR  MODERATE  MAJOR }

// ═══════════════════════════════════════════════════════════════
// ACCOUNTING SETTINGS
// ═══════════════════════════════════════════════════════════════

model AccountingSettings {
  id               String          @id @default(cuid())
  companyId        String          @unique
  company          Company         @relation(fields: [companyId], references: [id], onDelete: Cascade)

  reportingPeriod  ReportingPeriod @default(MONTHLY)
  fiscalYearStart  Int             @default(1)   // 1=Jan
  currency         String          @default("MAD")

  accountantEmail  String?
  accountantName   String?
  autoSendReport   Boolean         @default(false)
  reportFormat     ReportFormat    @default(PDF)

  updatedAt DateTime @updatedAt

  @@map("accounting_settings")
}

enum ReportingPeriod { WEEKLY  MONTHLY  QUARTERLY  ANNUAL }
enum ReportFormat    { PDF  CSV  BOTH }

// ═══════════════════════════════════════════════════════════════
// ADMIN PANEL
// ═══════════════════════════════════════════════════════════════

model AdminUser {
  id            String      @id @default(cuid())
  email         String      @unique
  firstName     String
  lastName      String
  passwordHash  String
  role          AdminRole   @default(SUPPORT)
  isActive      Boolean     @default(true)

  totpSecret    String?
  totpEnabled   Boolean     @default(false)

  lastLoginAt   DateTime?
  lastLoginIp   String?

  auditLogs     AuditLog[]
  permissions   AdminPermission[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("admin_users")
}

enum AdminRole { SUPER_ADMIN  ADMIN  SUPPORT  FINANCE  VIEWER }

model AdminPermission {
  id          String        @id @default(cuid())
  adminUserId String
  adminUser   AdminUser     @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
  resource    AdminResource
  actions     AdminAction[]

  @@unique([adminUserId, resource])
  @@map("admin_permissions")
}

enum AdminResource {
  COMPANIES  COMPANY_EMPLOYEES  SUBSCRIPTIONS  PAYMENTS
  OFFERS  RENTERS  ADMIN_USERS  AUDIT_LOGS  PLATFORM_METRICS
}

enum AdminAction { READ  CREATE  UPDATE  DELETE  SUSPEND  IMPERSONATE }

model AuditLog {
  id          String    @id @default(cuid())
  adminUserId String
  adminUser   AdminUser @relation(fields: [adminUserId], references: [id])

  action      String
  resource    String
  resourceId  String?
  companyId   String?

  before      Json?
  after       Json?

  ipAddress   String?
  userAgent   String?
  note        String?

  createdAt DateTime @default(now())

  @@index([adminUserId])
  @@index([companyId])
  @@index([action])
  @@map("audit_logs")
}