Files
root 7ff2dbb139
Build & Deploy / Build & Push Docker Image (push) Successful in 2m55s
Test / Type Check (all packages) (push) Successful in 54s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Failing after 48s
Test / Homepage Unit Tests (push) Successful in 43s
Test / Storefront Unit Tests (push) Failing after 40s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Failing after 42s
Test / API Integration Tests (push) Successful in 1m0s
chore: wire Carplace into dev and production stacks
2026-07-02 18:15:42 -04:00

2018 lines
61 KiB
Plaintext

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
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 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[]
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")
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")
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
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 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")
}