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 } 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 MARKETPLACE 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 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 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") 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]) providerInvoiceId String? amount Int currency String @default("MAD") status InvoiceStatus amanpayTransactionId String? @unique paypalCaptureId 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[] isListedOnMarketplace Boolean @default(true) marketplaceRating 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? role EmployeeRole @default(AGENT) preferredLanguage String @default("en") isActive Boolean @default(true) notifications Notification[] @relation("EmployeeNotifications") notificationPreferences NotificationPreference[] 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") 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) marketplaceRef 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? 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 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 paymentMethod String? paidAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([reservationId]) @@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 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") } // ═══════════════════════════════════════════════════════════════ // 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 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([]) 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") }