generator client { provider = "prisma-client-js" output = "../generated" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ═══════════════════════════════════════════════════════════════ // ENUMS // ═══════════════════════════════════════════════════════════════ enum CompanyStatus { PENDING TRIALING ACTIVE PAST_DUE SUSPENDED CANCELLED } enum Plan { STARTER GROWTH PRO } enum BillingPeriod { MONTHLY ANNUAL } enum SubscriptionStatus { TRIALING ACTIVE PAYMENT_PENDING PAST_DUE SUSPENDED CANCELLED EXPIRED PAUSED UNPAID } enum InvoiceStatus { PENDING PAID FAILED REFUNDED VOIDED } enum BillingInvoiceTerms { DUE_ON_RECEIPT NET_7 NET_15 NET_30 NET_45 NET_60 } enum BillingInvoiceStatus { DRAFT OPEN PAID PARTIALLY_PAID PAYMENT_PENDING PAST_DUE VOID UNCOLLECTIBLE REFUNDED PARTIALLY_REFUNDED } enum BillingInvoiceType { SUBSCRIPTION_INITIAL SUBSCRIPTION_RENEWAL TRIAL_CONVERSION SUBSCRIPTION_UPGRADE SUBSCRIPTION_DOWNGRADE_CREDIT USAGE_BASED ONE_TIME MANUAL CORRECTION CANCELLATION_FEE } enum BillingLineItemType { SUBSCRIPTION_FEE SEAT_FEE USAGE_FEE SETUP_FEE DISCOUNT TAX CREDIT PRORATION REFUND_ADJUSTMENT MANUAL_ADJUSTMENT PROFESSIONAL_SERVICES CANCELLATION_FEE } enum BillingPaymentMethodType { CARD ACH_DEBIT BANK_TRANSFER WIRE_TRANSFER MANUAL_INVOICE PURCHASE_ORDER } enum BillingPaymentIntentStatus { REQUIRES_PAYMENT_METHOD REQUIRES_CONFIRMATION REQUIRES_ACTION PROCESSING SUCCEEDED FAILED CANCELED REFUNDED PARTIALLY_REFUNDED } enum BillingPaymentAttemptStatus { PENDING PROCESSING SUCCEEDED FAILED CANCELED REFUNDED PARTIALLY_REFUNDED } enum BillingRefundStatus { PENDING SUCCEEDED FAILED } enum BillingCreditNoteStatus { ISSUED APPLIED VOID } enum PaymentProvider { AMANPAY PAYPAL STRIPE MANUAL } enum PaymentStatus { PENDING SUCCEEDED FAILED REFUNDED PARTIALLY_REFUNDED } enum PaymentType { CHARGE DEPOSIT REFUND } enum EmployeeRole { OWNER MANAGER AGENT } enum MenuItemType { INTERNAL_PAGE EXTERNAL_LINK PARENT_MENU SECTION_LABEL DIVIDER } enum VehicleStatus { AVAILABLE RESERVED READY RENTED RETURNED NEEDS_CLEANING MAINTENANCE DAMAGE_REVIEW OUT_OF_SERVICE } enum VehicleCategory { ECONOMY COMPACT MIDSIZE FULLSIZE SUV LUXURY VAN TRUCK } enum Transmission { AUTOMATIC MANUAL } enum FuelType { GASOLINE DIESEL ELECTRIC HYBRID } enum VehiclePricingMode { MANUAL AUTOMATIC } enum VehiclePricingRuleType { DATE_RANGE SEASONAL HOLIDAY WEEKEND SPECIAL_EVENT MANUAL_OVERRIDE } enum VehiclePriceChangeSource { CONFIG_UPDATE RULE_CREATED RULE_UPDATED RULE_DELETED MANUAL_OVERRIDE } enum OfferType { PERCENTAGE FIXED_AMOUNT FREE_DAY SPECIAL_RATE } enum ReservationStatus { DRAFT CONFIRMED ACTIVE COMPLETED CANCELLED NO_SHOW } enum BookingSource { DASHBOARD PUBLIC_SITE CARPLACE API } enum ReservationPhotoType { PICKUP DROPOFF } 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 FuelPolicyType { FULL_TO_FULL FULL_TO_EMPTY SAME_TO_SAME PREPAID FREE } enum InspectionType { CHECKIN CHECKOUT } enum FuelLevel { FULL SEVEN_EIGHTHS THREE_QUARTERS FIVE_EIGHTHS HALF THREE_EIGHTHS QUARTER ONE_EIGHTH EMPTY } enum DiagramView { TOP FRONT REAR LEFT RIGHT } enum DamageType { SCRATCH DENT CRACK CHIP MISSING STAIN OTHER } enum DamageSeverity { MINOR MODERATE MAJOR } enum ReportingPeriod { WEEKLY MONTHLY QUARTERLY ANNUAL } enum ReportFormat { PDF CSV BOTH } enum AdminRole { SUPER_ADMIN ADMIN SUPPORT FINANCE VIEWER } enum AdminResource { COMPANIES COMPANY_EMPLOYEES SUBSCRIPTIONS PAYMENTS OFFERS RENTERS ADMIN_USERS AUDIT_LOGS PLATFORM_METRICS } enum AdminAction { READ CREATE UPDATE DELETE SUSPEND IMPERSONATE } enum NotificationType { ACCOUNT_CREATED NEW_BOOKING BOOKING_CANCELLED PAYMENT_RECEIVED PAYMENT_FAILED DOCUMENTS_REQUIRED PAYMENT_REQUIRED 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 FeedbackCategory { BOOKING PICKUP VEHICLE_CLEANLINESS VEHICLE_CONDITION STAFF_SERVICE PRICING DEPOSIT INSURANCE DAMAGE_CLAIM RETURN_PROCESS COMMUNICATION ROADSIDE_ASSISTANCE BILLING OTHER } enum ComplaintSeverity { LEVEL_1 LEVEL_2 LEVEL_3 } enum ComplaintStatus { OPEN INVESTIGATING RESOLVED CLOSED } enum NotificationChannel { EMAIL SMS WHATSAPP IN_APP PUSH } enum NotificationStatus { PENDING SENT DELIVERED FAILED READ } enum NotificationRecipientType { EMPLOYEE RENTER } enum NotificationDeliveryStatus { PENDING QUEUED SENT DELIVERED FAILED SKIPPED DEAD_LETTER } enum NotificationOutboxStatus { PENDING PUBLISHED FAILED } enum CalendarBlockType { MANUAL MAINTENANCE } // ═══════════════════════════════════════════════════════════════ // 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) subscriptionPaymentRef String? subscription Subscription? billingAccounts BillingAccount[] billingInvoices BillingInvoice[] brand BrandSettings? employees Employee[] vehicles Vehicle[] offers Offer[] reservations Reservation[] customers Customer[] rentalPayments RentalPayment[] subscriptionInvoices SubscriptionInvoice[] contractSettings ContractSettings? accountingSettings AccountingSettings? insurancePolicies InsurancePolicy[] pricingRules PricingRule[] notifications Notification[] @relation("CompanyNotifications") notificationEvents NotificationEvent[] notificationDefaults CompanyNotificationPreference[] complaints Complaint[] companyMenuItems CompanyMenuItem[] apiKeys CompanyApiKey[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt BillingEvent BillingEvent[] @@map("companies") } model CompanyApiKey { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) name String prefix String @unique keyHash String lastUsedAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) @@index([companyId]) @@map("company_api_keys") } 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") trialStartAt DateTime? trialEndAt DateTime? trialUsed Boolean @default(false) currentPeriodStart DateTime? currentPeriodEnd DateTime? paymentPendingSince DateTime? paymentDueAt DateTime? pastDueSince DateTime? suspendedAt DateTime? endedAt DateTime? cancelledAt DateTime? cancelAtPeriodEnd Boolean @default(false) retryCount Int @default(0) maxRetryCount Int @default(5) invoices SubscriptionInvoice[] billingInvoices BillingInvoice[] events SubscriptionEvent[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt BillingEvent BillingEvent[] @@map("subscriptions") } model SubscriptionEvent { id String @id @default(cuid()) subscriptionId String subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade) companyId String eventType String source String @default("system") payload Json @default("{}") occurredAt DateTime createdAt DateTime @default(now()) @@index([subscriptionId]) @@index([companyId]) @@map("subscription_events") } 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]) requestedPlan Plan? requestedBillingPeriod BillingPeriod? providerInvoiceId String? amount Int currency String @default("MAD") status InvoiceStatus amanpayTransactionId String? @unique paypalCaptureId String? @unique stripeCheckoutSessionId String? @unique paymentProvider PaymentProvider @default(AMANPAY) billingInvoiceId String? @unique billingInvoice BillingInvoice? @relation(fields: [billingInvoiceId], references: [id]) dueAt DateTime? paidAt DateTime? failedAt DateTime? voidedAt DateTime? attempts PaymentAttempt[] createdAt DateTime @default(now()) @@index([companyId]) @@map("subscription_invoices") } model BillingAccount { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) isPrimary Boolean @default(true) legalName String billingEmail String preferredLanguage String @default("en") billingAddress Json? taxId String? taxExempt Boolean @default(false) defaultCurrency String @default("MAD") defaultPaymentMethodId String? defaultPaymentMethod BillingPaymentMethod? @relation("BillingAccountDefaultPaymentMethod", fields: [defaultPaymentMethodId], references: [id]) invoiceTerms BillingInvoiceTerms @default(DUE_ON_RECEIPT) netTermsDays Int @default(0) providerCustomerId String? dunningPaused Boolean @default(false) dunningPausedAt DateTime? dunningPausedBy String? metadata Json @default("{}") paymentMethods BillingPaymentMethod[] @relation("BillingAccountPaymentMethods") invoices BillingInvoice[] paymentIntents BillingPaymentIntent[] paymentAttempts BillingPaymentAttempt[] creditBalances BillingCreditBalance[] creditLedgerEntries BillingCreditLedgerEntry[] creditNotes BillingCreditNote[] refunds BillingRefund[] events BillingEvent[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([companyId, isPrimary]) @@map("billing_accounts") } model BillingPaymentMethod { id String @id @default(cuid()) billingAccountId String billingAccount BillingAccount @relation("BillingAccountPaymentMethods", fields: [billingAccountId], references: [id], onDelete: Cascade) type BillingPaymentMethodType providerPaymentMethodId String? label String? brand String? last4 String? expMonth Int? expYear Int? isDefault Boolean @default(false) isActive Boolean @default(true) metadata Json @default("{}") defaultForBillingAccounts BillingAccount[] @relation("BillingAccountDefaultPaymentMethod") paymentIntents BillingPaymentIntent[] paymentAttempts BillingPaymentAttempt[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([billingAccountId]) @@map("billing_payment_methods") } model BillingInvoice { id String @id @default(cuid()) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) subscriptionId String? subscription Subscription? @relation(fields: [subscriptionId], references: [id]) legacySubscriptionInvoice SubscriptionInvoice? invoiceNumber String? @unique invoiceSequence Int? @unique invoiceType BillingInvoiceType status BillingInvoiceStatus @default(DRAFT) currency String @default("MAD") subtotalAmount Int @default(0) discountAmount Int @default(0) creditAmount Int @default(0) taxAmount Int @default(0) totalAmount Int @default(0) amountPaid Int @default(0) amountDue Int @default(0) invoiceDate DateTime? dueAt DateTime? finalizedAt DateTime? paidAt DateTime? voidedAt DateTime? markedUncollectibleAt DateTime? billingName String? billingEmail String? billingAddress Json? providerInvoiceId String? paymentProvider PaymentProvider? isSubscriptionBlocking Boolean @default(false) adminReason String? metadata Json @default("{}") createdByAdminId String? lineItems BillingInvoiceLineItem[] paymentIntents BillingPaymentIntent[] paymentAttempts BillingPaymentAttempt[] taxRecords BillingTaxRecord[] creditNotes BillingCreditNote[] refunds BillingRefund[] events BillingEvent[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt BillingCreditLedgerEntry BillingCreditLedgerEntry[] @@index([billingAccountId]) @@index([companyId]) @@index([subscriptionId]) @@map("billing_invoices") } model BillingInvoiceLineItem { id String @id @default(cuid()) invoiceId String invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) subscriptionId String? plan Plan? type BillingLineItemType description String quantity Int @default(1) unitAmount Int amount Int currency String @default("MAD") periodStart DateTime? periodEnd DateTime? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([invoiceId]) @@map("billing_invoice_line_items") } model BillingPaymentIntent { id String @id @default(cuid()) invoiceId String invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) paymentMethodId String? paymentMethod BillingPaymentMethod? @relation(fields: [paymentMethodId], references: [id]) providerPaymentIntentId String? status BillingPaymentIntentStatus @default(REQUIRES_PAYMENT_METHOD) amount Int currency String @default("MAD") metadata Json @default("{}") paymentAttempts BillingPaymentAttempt[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([invoiceId]) @@index([billingAccountId]) @@map("billing_payment_intents") } model BillingPaymentAttempt { id String @id @default(cuid()) invoiceId String invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) paymentIntentId String? paymentIntent BillingPaymentIntent? @relation(fields: [paymentIntentId], references: [id]) paymentMethodId String? paymentMethod BillingPaymentMethod? @relation(fields: [paymentMethodId], references: [id]) providerPaymentId String? status BillingPaymentAttemptStatus amount Int currency String @default("MAD") failureCode String? failureMessage String? attemptedAt DateTime metadata Json @default("{}") refunds BillingRefund[] createdAt DateTime @default(now()) @@index([invoiceId]) @@index([billingAccountId]) @@map("billing_payment_attempts") } model BillingCreditBalance { id String @id @default(cuid()) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) currency String @default("MAD") balanceAmount Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([billingAccountId, currency]) @@map("billing_credit_balances") } model BillingCreditLedgerEntry { id String @id @default(cuid()) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) invoiceId String? invoice BillingInvoice? @relation(fields: [invoiceId], references: [id], onDelete: SetNull) type String amount Int currency String @default("MAD") reason String createdBy String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([billingAccountId]) @@map("billing_credit_ledger_entries") } model BillingCreditNote { id String @id @default(cuid()) invoiceId String invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) amount Int currency String @default("MAD") reason String status BillingCreditNoteStatus @default(ISSUED) createdBy String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([invoiceId]) @@index([billingAccountId]) @@map("billing_credit_notes") } model BillingRefund { id String @id @default(cuid()) invoiceId String invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) paymentAttemptId String? paymentAttempt BillingPaymentAttempt? @relation(fields: [paymentAttemptId], references: [id], onDelete: SetNull) billingAccountId String billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade) amount Int currency String @default("MAD") reason String status BillingRefundStatus @default(PENDING) providerRefundId String? createdBy String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([invoiceId]) @@index([billingAccountId]) @@map("billing_refunds") } model BillingTaxRecord { id String @id @default(cuid()) invoiceId String invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) jurisdiction String? taxRate Float? taxAmount Int taxType String? taxExempt Boolean @default(false) exemptionReason String? providerTaxId String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([invoiceId]) @@map("billing_tax_records") } model BillingEvent { id String @id @default(cuid()) billingAccountId String? billingAccount BillingAccount? @relation(fields: [billingAccountId], references: [id], onDelete: SetNull) invoiceId String? invoice BillingInvoice? @relation(fields: [invoiceId], references: [id], onDelete: SetNull) subscriptionId String? subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull) companyId String? company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull) eventType String source String payload Json @default("{}") occurredAt DateTime createdAt DateTime @default(now()) @@index([billingAccountId]) @@index([invoiceId]) @@index([subscriptionId]) @@index([companyId]) @@map("billing_events") } model PaymentAttempt { id String @id @default(cuid()) invoiceId String invoice SubscriptionInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) subscriptionId String providerPaymentId String? status String failureCode String? failureMessage String? attemptedAt DateTime createdAt DateTime @default(now()) @@index([invoiceId]) @@map("payment_attempts") } 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? publicEmail String? publicPhone String? publicAddress String? publicCity String? publicCountry String? websiteUrl String? whatsappNumber String? facebookUrl String? instagramUrl String? defaultLocale String @default("en") defaultCurrency String @default("MAD") amanpayMerchantId String? amanpaySecretKey String? paypalEmail String? paypalMerchantId String? paymentMethodsEnabled PaymentProvider[] isListedOnCarplace Boolean @default(true) carplaceRating Float? homePageConfig Json? @map("home_page_config") menuConfig Json? @map("menu_config") updatedAt DateTime @updatedAt @@map("brand_settings") } 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? passwordHash String? passwordResetToken String? @unique passwordResetExpiresAt DateTime? emailVerified DateTime? emailVerificationToken String? @unique role EmployeeRole @default(AGENT) preferredLanguage String @default("en") isActive Boolean @default(true) notifications Notification[] @relation("EmployeeNotifications") notificationRecipients NotificationRecipient[] notificationPreferences NotificationPreference[] recordedRentalPayments RentalPayment[] @relation("RecordedRentalPayments") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@map("employees") } 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[] dailyRate Int status VehicleStatus @default(AVAILABLE) photos String[] mileage Int? notes String? isPublished Boolean @default(true) pickupLocations String[] @default([]) allowDifferentDropoff Boolean @default(false) dropoffLocations String[] @default([]) reservations Reservation[] maintenance MaintenanceLog[] offerVehicles OfferVehicle[] calendarBlocks VehicleCalendarBlock[] pricingConfiguration VehiclePricingConfiguration? priceHistory VehiclePriceHistory[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([companyId, status]) @@index([companyId, isPublished]) @@map("vehicles") } model VehiclePricingConfiguration { id String @id @default(cuid()) vehicleId String @unique vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) pricingMode VehiclePricingMode @default(MANUAL) baseDailyRate Int? weeklyRate Int? weekendRate Int? holidayRate Int? monthlyRate Int? longTermDailyRate Int? minimumDailyRate Int? maximumDailyRate Int? maxDailyPriceMovementPct Int @default(10) automaticPricingEnabled Boolean @default(false) approvalRequired Boolean @default(false) rules VehiclePricingRule[] history VehiclePriceHistory[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("vehicle_pricing_configurations") } model VehiclePricingRule { id String @id @default(cuid()) configurationId String configuration VehiclePricingConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade) name String ruleType VehiclePricingRuleType @default(DATE_RANGE) startDate DateTime endDate DateTime dailyRate Int? weeklyRate Int? weekendRate Int? monthlyRate Int? minDailyRate Int? maxDailyRate Int? automaticAdjustmentPct Int? priceAdjustment Int? isActive Boolean @default(true) sortOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([configurationId]) @@index([configurationId, startDate, endDate]) @@map("vehicle_pricing_rules") } model VehiclePriceHistory { id String @id @default(cuid()) configurationId String configuration VehiclePricingConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade) vehicleId String vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) source VehiclePriceChangeSource @default(CONFIG_UPDATE) changedByEmployeeId String? previousDailyRate Int? nextDailyRate Int? previousWeeklyRate Int? nextWeeklyRate Int? note String? effectiveFrom DateTime? createdAt DateTime @default(now()) @@index([vehicleId, createdAt]) @@index([configurationId, createdAt]) @@map("vehicle_price_history") } 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? 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) isFeatured Boolean @default(false) vehicles OfferVehicle[] reservations Reservation[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([isPublic, isActive, validUntil]) @@map("offers") } 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") notificationRecipients NotificationRecipient[] 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") } model Customer { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) renterId String? firstName String lastName String email String phone String? driverLicense String? dateOfBirth DateTime? nationality String? address Json? notes String? flagged Boolean @default(false) flagReason String? licenseExpiry DateTime? licenseIssuedAt DateTime? licenseCountry String? licenseImageUrl String? licenseNumber String? licenseCategory String? licenseExpired Boolean @default(false) licenseExpiringSoon Boolean @default(false) licenseValidationStatus LicenseStatus @default(PENDING) licenseApprovedBy String? licenseApprovedAt DateTime? licenseApprovalNote String? reservations Reservation[] complaints Complaint[] reviewOptOut Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([companyId, email]) @@index([companyId]) @@map("customers") } 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? renter Renter? @relation("RenterReservations", fields: [renterId], references: [id]) offerId String? offer Offer? @relation(fields: [offerId], references: [id]) promoCodeUsed String? vehicleCategory VehicleCategory? source BookingSource @default(DASHBOARD) carplaceRef String? 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) paymentStatus String @default("UNPAID") paidAmount Int @default(0) checkedInAt DateTime? checkedOutAt DateTime? checkInMileage Int? checkOutMileage Int? notes String? cancelReason String? cancelledBy CancelledBy? bookingReference String? @unique contractNumber String? @unique invoiceNumber String? @unique reviewToken String? @unique checkInFuelLevel String? checkOutFuelLevel String? reviewRequestSentAt DateTime? reviewReminderSentAt DateTime? reviewFinalReminderSentAt DateTime? reviewPaused Boolean @default(false) insurances ReservationInsurance[] insuranceTotal Int @default(0) additionalDrivers AdditionalDriver[] additionalDriverTotal Int @default(0) pricingRulesApplied Json? pricingRulesTotal Int @default(0) damageReports DamageReport[] rentalPayments RentalPayment[] inspections DamageInspection[] photos ReservationPhoto[] review Review? complaints Complaint[] publicAccess ReservationPublicAccess[] damageChargeAmount Int? damageChargeNote String? extras Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([companyId, status]) @@index([vehicleId, startDate, endDate]) @@index([renterId]) @@map("reservations") } model ReservationPublicAccess { id String @id @default(cuid()) reservationId String reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade) tokenHash String @unique expiresAt DateTime usedAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) @@index([reservationId]) @@map("reservation_public_access") } model CarplaceFunnelEvent { id String @id @default(cuid()) eventName String companyId String? companySlug String vehicleId String renterId String? sessionId String? path String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([eventName, createdAt]) @@index([companyId, createdAt]) @@index([companySlug, createdAt]) @@index([vehicleId, createdAt]) @@index([renterId, createdAt]) @@map("carplace_funnel_events") } model WebhookEvent { id String @id @default(cuid()) provider String providerEventId String eventType String status String payloadHash String errorMessage String? receivedAt DateTime @default(now()) processedAt DateTime? @@unique([provider, providerEventId]) @@index([provider, status]) @@map("webhook_events") } 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 currency String @default("MAD") status PaymentStatus @default(PENDING) type PaymentType @default(CHARGE) paymentProvider PaymentProvider amanpayTransactionId String? @unique paypalCaptureId String? @unique stripeCheckoutSessionId String? @unique stripePaymentIntentId String? @unique paymentMethod String? reference String? note String? receivedAt DateTime? recordedByEmployeeId String? recordedByEmployee Employee? @relation("RecordedRentalPayments", fields: [recordedByEmployeeId], references: [id]) idempotencyKey String? paidAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([reservationId]) @@unique([companyId, idempotencyKey]) @@map("rental_payments") } 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? category FeedbackCategory? complaints Complaint[] createdAt DateTime @default(now()) @@index([companyId]) @@map("reviews") } model Complaint { 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]) reviewId String? review Review? @relation(fields: [reviewId], references: [id]) customerId String? customer Customer? @relation(fields: [customerId], references: [id]) severity ComplaintSeverity @default(LEVEL_1) status ComplaintStatus @default(OPEN) category FeedbackCategory subject String description String? resolution String? notes String? assignedTo String? resolvedAt DateTime? resolvedBy String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([reservationId]) @@map("complaints") } // ═══════════════════════════════════════════════════════════════ // 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 templateKey String? locale String @default("en") status NotificationStatus @default(PENDING) sentAt DateTime? failReason String? readAt DateTime? providerMessageId String? createdAt DateTime @default(now()) @@index([companyId]) @@index([renterId]) @@map("notifications") } model NotificationEvent { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) type NotificationType templateKey String? locale String @default("en") title String body String data Json? sourceType String sourceId String idempotencyKey String recipients NotificationRecipient[] outboxEntries NotificationOutbox[] createdAt DateTime @default(now()) @@unique([companyId, idempotencyKey]) @@index([companyId, type, createdAt]) @@map("notification_events") } model NotificationRecipient { id String @id @default(cuid()) notificationEventId String notificationEvent NotificationEvent @relation(fields: [notificationEventId], references: [id], onDelete: Cascade) recipientType NotificationRecipientType employeeId String? employee Employee? @relation(fields: [employeeId], references: [id], onDelete: Cascade) renterId String? renter Renter? @relation(fields: [renterId], references: [id], onDelete: Cascade) readAt DateTime? archivedAt DateTime? deliveries NotificationDelivery[] createdAt DateTime @default(now()) @@index([employeeId, readAt, createdAt]) @@index([renterId, readAt, createdAt]) @@index([notificationEventId]) @@map("notification_recipients") } model NotificationDelivery { id String @id @default(cuid()) notificationRecipientId String notificationRecipient NotificationRecipient @relation(fields: [notificationRecipientId], references: [id], onDelete: Cascade) channel NotificationChannel status NotificationDeliveryStatus @default(PENDING) attemptCount Int @default(0) lastAttemptAt DateTime? nextAttemptAt DateTime? provider String? providerMessageId String? failureCode String? failureReason String? preferenceDecision String? sentAt DateTime? deliveredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([notificationRecipientId, channel]) @@index([channel, status, nextAttemptAt]) @@map("notification_deliveries") } model NotificationOutbox { id String @id @default(cuid()) notificationEventId String notificationEvent NotificationEvent @relation(fields: [notificationEventId], references: [id], onDelete: Cascade) status NotificationOutboxStatus @default(PENDING) payload Json publishedAt DateTime? failureReason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, createdAt]) @@map("notification_outbox") } model NotificationTemplate { id String @id @default(cuid()) templateKey String category String channel NotificationChannel locale String subject String? body String requiredVariables Json? optionalVariables Json? version Int @default(1) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([templateKey, channel, locale, version]) @@index([templateKey, channel, locale, isActive]) @@map("notification_templates") } 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") } model CompanyNotificationPreference { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) notificationType NotificationType channel NotificationChannel enabled Boolean @default(true) @@unique([companyId, notificationType, channel]) @@map("company_notification_preferences") } // ═══════════════════════════════════════════════════════════════ // MAINTENANCE // ═══════════════════════════════════════════════════════════════ 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") } model VehicleCalendarBlock { id String @id @default(cuid()) vehicleId String vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) startDate DateTime @db.Date endDate DateTime @db.Date reason String? type CalendarBlockType @default(MANUAL) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([vehicleId]) @@index([vehicleId, startDate, endDate]) @@map("vehicle_calendar_blocks") } // ═══════════════════════════════════════════════════════════════ // DOCUMENTS — CONTRACT SETTINGS // ═══════════════════════════════════════════════════════════════ model ContractSettings { id String @id @default(cuid()) companyId String @unique company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) legalName String? registrationNumber String? taxId String? terms String @default("") fuelPolicy String @default("The vehicle will be provided with the fuel level or electric battery charge percentage recorded on the rental agreement. The renter must return the vehicle with the same fuel level or, for electric vehicles, the same battery charge percentage recorded at pickup.\n\nIf the vehicle is returned with less fuel or a lower electric battery charge percentage, the renter will be charged for the missing fuel or charging shortfall at the applicable refueling or charging rate, plus any configured service fee. Charges are based on the vehicle fuel gauge or battery charge percentage and the levels recorded at the start and end of the rental.\n\nFuel or charging purchased during the rental is the renter responsibility and is not refundable. The renter should retain fuel or charging receipts until the vehicle has been returned and the rental agreement has been closed.\n\nThe renter must use the correct fuel type or charging method specified for the vehicle. Any damage caused by using the wrong fuel, charger, connector, or charging method will be charged to the renter.") 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 responsible for inspecting the vehicle at collection and confirming that all existing damage is recorded on the rental agreement or vehicle condition report. Any unrecorded damage should be reported before the vehicle is driven.\n\nThe vehicle must be returned in the same condition in which it was provided, allowing for reasonable wear from normal use.\n\nThe renter may be responsible for new bodywork, paint, glass, tyre, wheel, or interior damage; damage caused by collision, misuse, negligence, or improper driving; damage resulting from driving on unsuitable roads or in prohibited areas; damage caused by an unauthorised driver; damage caused by incorrect fuel, lost keys, or failure to secure the vehicle; and towing, recovery, assessment, repair, administration, and loss-of-use charges where applicable.\n\nAny damage waiver or protection package is subject to its stated exclusions and excess or deductible. It does not automatically remove all financial responsibility.\n\nThe renter must report any accident, theft, vandalism, or damage as soon as reasonably possible and must follow the company reporting instructions. Where required, the renter must also contact the police and obtain an official report or reference number.\n\nThe renter must not arrange or authorise repairs without prior written approval from the rental company, except where necessary to protect personal safety or prevent further immediate damage.\n\nDamage charges will be supported by available inspection records, photographs, repair estimates, invoices, or other reasonable evidence. Any security deposit may be held or applied toward valid charges under the rental agreement.") additionalDriverPolicy String @default("Only drivers listed and approved on the rental agreement are permitted to drive the vehicle.\n\nThis driver policy applies to the principal renter and every additional driver. Every driver must meet the minimum age requirement, present a valid driving licence accepted in the rental location, provide any additional identification reasonably requested, and comply with all terms of the rental agreement.\n\nDrivers aged 25 or older qualify for the normal rental rate. Drivers aged 18 to 24 may be accepted subject to an additional young driver fee. The fee may apply to the principal renter or to any additional driver in this age range, according to the selected charge setting and pricing rules.\n\nAn additional driver fee may apply per day or per rental according to the selected charge setting. The fee and any applicable taxes will be confirmed before the rental begins.\n\nThe primary renter remains responsible for the vehicle and for all charges, damage, fines, penalties, and breaches of the rental agreement, regardless of which approved driver was operating the vehicle.\n\nAllowing an unauthorised person to drive the vehicle is a breach of the rental agreement and may invalidate any damage waiver, protection package, or insurance coverage.") additionalClauses String[] @default([]) signatureRequired Boolean @default(true) contractFooterNote String? invoiceFooterNote String? showTax Boolean @default(false) taxRate Float? taxLabel String? contractPrefix String @default("CNT") invoicePrefix String @default("INV") fuelPolicyType FuelPolicyType @default(FULL_TO_FULL) fuelPolicyNote String? fuelChargePerLiter Int? fuelShortfallFee Int? lateFeePerHour Int? taxLines Json? lastContractSeq Int @default(0) lastInvoiceSeq Int @default(0) additionalDriverCharge AdditionalDriverCharge @default(FREE) additionalDriverDailyRate Int @default(0) additionalDriverFlatRate Int @default(0) updatedAt DateTime @updatedAt @@map("contract_settings") } // ═══════════════════════════════════════════════════════════════ // INSURANCE // ═══════════════════════════════════════════════════════════════ 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 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]) policyName String policyType InsuranceType chargeType InsuranceChargeType chargeValue Int totalCharge Int @@unique([reservationId, insurancePolicyId]) @@map("reservation_insurances") } // ═══════════════════════════════════════════════════════════════ // ADDITIONAL DRIVERS // ═══════════════════════════════════════════════════════════════ 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 REPORTS // ═══════════════════════════════════════════════════════════════ model DamageReport { id String @id @default(cuid()) reservationId String reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade) companyId String type DamageReportType damages Json photos String[] fuelLevel FuelLevel @default(FULL) mileage Int? inspectedAt DateTime @default(now()) inspectedBy String? customerSignedAt DateTime? customerName String? @@unique([reservationId, type]) @@index([companyId]) @@map("damage_reports") } 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? generalCondition String? employeeNotes String? customerAgreed Boolean @default(false) inspectedAt DateTime @default(now()) inspectedBy String? damagePoints DamagePoint[] createdAt DateTime @default(now()) @@unique([reservationId, type]) @@index([reservationId]) @@index([companyId]) @@map("damage_inspections") } model DamagePoint { id String @id @default(cuid()) inspectionId String inspection DamageInspection @relation(fields: [inspectionId], references: [id], onDelete: Cascade) viewType DiagramView x Float y Float damageType DamageType severity DamageSeverity @default(MINOR) description String? isPreExisting Boolean @default(false) @@index([inspectionId]) @@map("damage_points") } // ═══════════════════════════════════════════════════════════════ // PRICING RULES // ═══════════════════════════════════════════════════════════════ 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 adjustmentType PriceAdjustmentType adjustmentValue Int isActive Boolean @default(true) description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@map("pricing_rules") } // ═══════════════════════════════════════════════════════════════ // ACCOUNTING // ═══════════════════════════════════════════════════════════════ 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) currency String @default("MAD") accountantEmail String? accountantName String? autoSendReport Boolean @default(false) reportFormat ReportFormat @default(PDF) updatedAt DateTime @updatedAt @@map("accounting_settings") } // ═══════════════════════════════════════════════════════════════ // 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? passwordResetToken String? @unique passwordResetExpiresAt DateTime? auditLogs AuditLog[] permissions AdminPermission[] recoveryCodes AdminRecoveryCode[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("admin_users") } 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") } model AdminRecoveryCode { id String @id @default(cuid()) adminUserId String adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade) codeHash String usedAt DateTime? createdAt DateTime @default(now()) @@index([adminUserId, usedAt]) @@map("admin_recovery_codes") } 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") } model PricingConfig { id String @id @default(cuid()) plan String billingPeriod String amount Int updatedAt DateTime @updatedAt updatedBy String? @@unique([plan, billingPeriod]) @@map("pricing_configs") } model PlanFeature { id String @id @default(cuid()) plan Plan label String sortOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([plan, sortOrder]) @@map("plan_features") } model MenuItem { id String @id @default(cuid()) systemKey String? @unique label String itemType MenuItemType @default(INTERNAL_PAGE) routeOrUrl String? icon String? parentId String? parent MenuItem? @relation("MenuItemHierarchy", fields: [parentId], references: [id], onDelete: SetNull) children MenuItem[] @relation("MenuItemHierarchy") displayOrder Int @default(0) openInNewTab Boolean @default(false) isRequired Boolean @default(false) isActive Boolean @default(true) createdBy String? updatedBy String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt subscriptionAssignments SubscriptionMenuItem[] companyAssignments CompanyMenuItem[] roleVisibilities MenuItemRoleVisibility[] @@index([parentId]) @@index([isActive, displayOrder]) @@map("menu_items") } model SubscriptionMenuItem { id String @id @default(cuid()) plan Plan menuItemId String menuItem MenuItem @relation(fields: [menuItemId], references: [id], onDelete: Cascade) displayOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([plan, menuItemId]) @@index([menuItemId]) @@index([plan, displayOrder]) @@map("subscription_menu_items") } model CompanyMenuItem { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) menuItemId String menuItem MenuItem @relation(fields: [menuItemId], references: [id], onDelete: Cascade) displayOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([companyId, menuItemId]) @@index([menuItemId]) @@index([companyId, displayOrder]) @@map("company_menu_items") } model MenuItemRoleVisibility { id String @id @default(cuid()) menuItemId String menuItem MenuItem @relation(fields: [menuItemId], references: [id], onDelete: Cascade) role EmployeeRole createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([menuItemId, role]) @@index([role]) @@map("menu_item_role_visibility") } model PricingPromotion { id String @id @default(cuid()) code String @unique name String description String? discountType String // 'PERCENTAGE' | 'FIXED' discountValue Int // percentage (1-100) or centimes for FIXED plans String[] // empty array = all plans periods String[] // empty array = all periods maxUses Int? usedCount Int @default(0) validFrom DateTime validUntil DateTime? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @@index([isActive]) @@map("pricing_promotions") } model ReservationPhoto { id String @id @default(cuid()) reservationId String reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade) type ReservationPhotoType url String createdAt DateTime @default(now()) @@index([reservationId]) @@map("reservation_photos") }