Files
carmanagement/packages/database/prisma/schema.prisma
T
root 057d41e1c2
Build & Push / Pipeline Tests (push) Failing after 1m5s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 52s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
add text input validation
2026-08-31 22:51:44 -04:00

2628 lines
91 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
ENTERPRISE
}
enum BillingPeriod {
MONTHLY
ANNUAL
}
enum SubscriptionStatus {
TRIALING
ACTIVE
PAYMENT_PENDING
PAST_DUE
SUSPENDED
CANCELLED
EXPIRED
PAUSED
UNPAID
}
enum SubscriptionUpgradeRequestType {
IMMEDIATE_PRORATED
AT_RENEWAL
TERM_RESET
}
enum SubscriptionUpgradeRequestStatus {
DRAFT
QUOTED
PAYMENT_PENDING
PAYMENT_REVIEW
CORRECTION_REQUIRED
APPROVED
ACTIVATED
SCHEDULED
REJECTED
EXPIRED
CANCELLED
ACTIVATION_FAILED
SUPERSEDED
}
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 {
BANK_TRANSFER
CHECK
}
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 SubscriptionCollectionMethod {
BANK_TRANSFER
CHECK
}
enum BillingPaymentChannel {
ONLINE
OFFLINE
}
enum ManualPaymentMethod {
BANK_TRANSFER
CHECK
}
enum ManualPaymentSubmissionStatus {
DRAFT
SUBMITTED
UNDER_REVIEW
APPROVED
REJECTED
}
enum ManualPaymentDocumentKind {
BANK_TRANSFER_RECEIPT
CHECK_COPY
OTHER_SUPPORTING_EVIDENCE
}
enum ManualPaymentDocumentScanStatus {
UPLOADED
SCANNING
CLEAN
QUARANTINED
SCAN_FAILED
}
enum CollectionsCaseStatus {
SCHEDULED
PRE_DUE
GRACE_PERIOD
RESOLVED
SUSPENDED
}
enum CollectionsCallTaskType {
PRE_EXPIRY_48H_CALL
PAYMENT_PROMISE_FOLLOW_UP
}
enum CollectionsCallTaskStatus {
OPEN
COMPLETED
CANCELLED
}
enum CollectionsCallOutcome {
CONTACTED
NO_ANSWER
PAYMENT_PROMISED
ISSUE_ESCALATED
}
enum CollectionsOverrideType {
PAYMENT_DISPUTE
MANUAL_EXTENSION
}
enum CollectionsOverrideStatus {
ACTIVE
REVOKED
EXPIRED
}
enum BillingRefundStatus {
PENDING
SUCCEEDED
FAILED
}
enum BillingCreditNoteStatus {
ISSUED
APPLIED
VOID
}
enum PaymentProvider {
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
SUBSCRIPTION_PAYMENT_DUE_14D
SUBSCRIPTION_PAYMENT_DUE_7D
SUBSCRIPTION_PAYMENT_DUE_48H
SUBSCRIPTION_PAYMENT_DUE_24H
SUBSCRIPTION_GRACE_DAILY
SUBSCRIPTION_GRACE_FINAL
COLLECTIONS_CALL_REQUIRED
MANUAL_PAYMENT_EVIDENCE_SUBMITTED
SUBSCRIPTION_PAYMENT_CONFIRMED
MANUAL_PAYMENT_EVIDENCE_REJECTED
COLLECTIONS_OVERRIDE_CHANGED
}
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
BILLING_CONTACT
ADMIN
}
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? @db.VarChar(20)
address Json?
status CompanyStatus @default(PENDING)
subscriptionPaymentRef String?
subscription Subscription?
subscriptionUpgradeRequests SubscriptionUpgradeRequest[]
subscriptionPlanHistory SubscriptionPlanHistory[]
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[]
billingContacts BillingContact[]
manualPaymentSubmissions ManualPaymentSubmission[]
manualPaymentDocuments ManualPaymentDocument[]
collectionsCases CollectionsCase[]
collectionsEvents CollectionsEvent[]
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[]
collectionsCases CollectionsCase[]
events SubscriptionEvent[]
upgradeRequests SubscriptionUpgradeRequest[]
planHistory SubscriptionPlanHistory[]
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
paymentProvider PaymentProvider @default(MANUAL)
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")
timezone String @default("Africa/Casablanca")
reminderLocalTime String @default("09:00")
enabledCommunicationLocales String[] @default(["en"])
defaultCommunicationLocale String @default("en")
collectionsOwnerAdminId String?
collectionsOwnerAdmin AdminUser? @relation("BillingAccountCollectionsOwner", fields: [collectionsOwnerAdminId], references: [id], onDelete: SetNull)
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[]
billingContacts BillingContact[]
manualPaymentSubmissions ManualPaymentSubmission[]
collectionsCases CollectionsCase[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@index([companyId, isPrimary])
@@map("billing_accounts")
}
model BillingContact {
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)
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
email String
locale String?
isPrimary Boolean @default(false)
receivePaymentNotices Boolean @default(true)
isActive Boolean @default(true)
verifiedAt DateTime?
notificationRecipients NotificationRecipient[]
callTasks CollectionsCallTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([billingAccountId, email])
@@index([companyId, isActive])
@@index([employeeId])
@@map("billing_contacts")
}
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?
subscriptionUpgradeRequest SubscriptionUpgradeRequest?
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?
collectionMethod SubscriptionCollectionMethod @default(BANK_TRANSFER)
requestedPlan Plan?
requestedBillingPeriod BillingPeriod?
checkoutIdempotencyKey String?
renewalKey String? @unique
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[]
manualPaymentSubmissions ManualPaymentSubmission[]
collectionsCase CollectionsCase?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
BillingCreditLedgerEntry BillingCreditLedgerEntry[]
@@unique([billingAccountId, checkoutIdempotencyKey])
@@index([billingAccountId])
@@index([companyId])
@@index([subscriptionId])
@@index([collectionMethod, status, dueAt])
@@map("billing_invoices")
}
model SubscriptionUpgradeRequest {
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], onDelete: Cascade)
requestType SubscriptionUpgradeRequestType
fromPlan Plan
fromBillingPeriod BillingPeriod
toPlan Plan
toBillingPeriod BillingPeriod
status SubscriptionUpgradeRequestStatus @default(DRAFT)
requestedByEmployeeId String
requestedByEmployee Employee @relation("SubscriptionUpgradeRequester", fields: [requestedByEmployeeId], references: [id])
requestedAt DateTime @default(now())
effectiveAt DateTime?
scheduledFor DateTime?
quoteId String? @unique
quote SubscriptionUpgradeQuote? @relation("ActiveSubscriptionUpgradeQuote", fields: [quoteId], references: [id])
quotes SubscriptionUpgradeQuote[] @relation("SubscriptionUpgradeQuotes")
companyLanguageSnapshot String @default("en")
billingTimezoneSnapshot String @default("Africa/Casablanca")
acceptedTermsVersion String?
acceptedAt DateTime?
cancelledAt DateTime?
cancelledByEmployeeId String?
cancellationReason String?
expiresAt DateTime?
billingInvoiceId String? @unique
billingInvoice BillingInvoice? @relation(fields: [billingInvoiceId], references: [id])
approvedPaymentAttemptId String?
approvedByAdminId String?
approvedByAdmin AdminUser? @relation("SubscriptionUpgradeApprover", fields: [approvedByAdminId], references: [id], onDelete: SetNull)
approvedAt DateTime?
activationKey String? @unique
activatedAt DateTime?
rejectionReason String?
correctionReason String?
version Int @default(1)
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId, status])
@@index([subscriptionId, status])
@@index([expiresAt])
@@index([scheduledFor])
@@map("subscription_upgrade_requests")
}
model SubscriptionUpgradeQuote {
id String @id @default(cuid())
upgradeRequestId String
upgradeRequest SubscriptionUpgradeRequest @relation("SubscriptionUpgradeQuotes", fields: [upgradeRequestId], references: [id], onDelete: Cascade)
activeForUpgradeRequest SubscriptionUpgradeRequest? @relation("ActiveSubscriptionUpgradeQuote")
currency String @default("MAD")
currentEligibleNetTermPrice Int
targetNetTermPrice Int
termStart DateTime
renewalDate DateTime
pricingEffectiveAt DateTime
termDays Int
remainingDays Int
prorationNumerator Int
prorationDenominator Int
currentCredit Int
targetRemainingValue Int
discountAmount Int @default(0)
taxAmount Int @default(0)
feeAmount Int @default(0)
subtotalAmount Int
totalAmount Int
calculationVersion String
inputSnapshot Json @default("{}")
integrityHash String
expiresAt DateTime
expiredAt DateTime?
createdAt DateTime @default(now())
@@index([upgradeRequestId])
@@index([expiresAt])
@@map("subscription_upgrade_quotes")
}
model SubscriptionPlanHistory {
id String @id @default(cuid())
subscriptionId String
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
previousPlan Plan
previousBillingPeriod BillingPeriod
newPlan Plan
newBillingPeriod BillingPeriod
changeType String
sourceUpgradeRequestId String?
approvedPaymentAttemptId String?
effectiveAt DateTime
previousTermStart DateTime?
previousTermEnd DateTime?
newTermStart DateTime?
newTermEnd DateTime?
actorType String
actorId String?
approvingAdminId String?
entitlementSnapshot Json @default("{}")
auditCorrelationId String
createdAt DateTime @default(now())
@@unique([sourceUpgradeRequestId])
@@index([subscriptionId])
@@index([companyId])
@@index([changeType])
@@map("subscription_plan_history")
}
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?
channel BillingPaymentChannel @default(ONLINE)
manualMethod ManualPaymentMethod?
externalReference String?
normalizedExternalReference String?
receivedAt DateTime?
confirmedAt DateTime?
confirmedByAdminId String?
confirmedByAdmin AdminUser? @relation("ManualPaymentConfirmingAdmin", fields: [confirmedByAdminId], references: [id], onDelete: SetNull)
idempotencyKey String?
note String?
status BillingPaymentAttemptStatus
amount Int
currency String @default("MAD")
failureCode String?
failureMessage String?
attemptedAt DateTime
metadata Json @default("{}")
refunds BillingRefund[]
manualPaymentSubmission ManualPaymentSubmission?
resolvedCollectionsCase CollectionsCase? @relation("CollectionsResolutionPayment")
createdAt DateTime @default(now())
@@unique([billingAccountId, idempotencyKey])
@@unique([billingAccountId, manualMethod, normalizedExternalReference])
@@index([invoiceId])
@@index([billingAccountId])
@@map("billing_payment_attempts")
}
model ManualPaymentSubmission {
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)
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
method ManualPaymentMethod
submittedReference String
normalizedSubmittedReference String
status ManualPaymentSubmissionStatus @default(DRAFT)
submittedByEmployeeId String
submittedByEmployee Employee @relation(fields: [submittedByEmployeeId], references: [id])
submittedAt DateTime?
reviewedByAdminId String?
reviewedByAdmin AdminUser? @relation("ManualPaymentReviewingAdmin", fields: [reviewedByAdminId], references: [id], onDelete: SetNull)
reviewedAt DateTime?
rejectionReason String?
idempotencyKey String
paymentAttemptId String? @unique
paymentAttempt BillingPaymentAttempt? @relation(fields: [paymentAttemptId], references: [id], onDelete: SetNull)
documents ManualPaymentDocument[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([billingAccountId, idempotencyKey])
@@index([invoiceId, status])
@@index([status, submittedAt])
@@map("manual_payment_submissions")
}
model ManualPaymentDocument {
id String @id @default(cuid())
submissionId String
submission ManualPaymentSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
invoiceId String
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
kind ManualPaymentDocumentKind
storageKey String @unique
originalFilename String
detectedMimeType String
detectedExtension String
byteSize Int
sha256 String
scanStatus ManualPaymentDocumentScanStatus @default(UPLOADED)
scannerResultCode String?
uploadedByEmployeeId String
uploadedByEmployee Employee @relation(fields: [uploadedByEmployeeId], references: [id])
uploadedAt DateTime @default(now())
deletedAt DateTime?
@@unique([submissionId, sha256])
@@index([invoiceId])
@@index([companyId])
@@index([scanStatus, uploadedAt])
@@map("manual_payment_documents")
}
model CollectionsCase {
id String @id @default(cuid())
invoiceId String @unique
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
subscriptionId String
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
billingAccountId String
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
status CollectionsCaseStatus @default(SCHEDULED)
originalExpirationAt DateTime
reminder14At DateTime
reminder7At DateTime
reminder48At DateTime
reminder24At DateTime
graceStartedAt DateTime?
finalSuspensionAt DateTime
resolvedAt DateTime?
suspendedAt DateTime?
nextActionAt DateTime?
collectionsOwnerAdminId String?
collectionsOwnerAdmin AdminUser? @relation("CollectionsCaseOwner", fields: [collectionsOwnerAdminId], references: [id], onDelete: SetNull)
resolutionPaymentAttemptId String? @unique
resolutionPaymentAttempt BillingPaymentAttempt? @relation("CollectionsResolutionPayment", fields: [resolutionPaymentAttemptId], references: [id], onDelete: SetNull)
processingLeaseUntil DateTime?
processingBy String?
version Int @default(0)
tasks CollectionsCallTask[]
overrides CollectionsOverride[]
events CollectionsEvent[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([subscriptionId, originalExpirationAt])
@@index([status, nextActionAt])
@@map("collections_cases")
}
model CollectionsCallTask {
id String @id @default(cuid())
collectionsCaseId String
collectionsCase CollectionsCase @relation(fields: [collectionsCaseId], references: [id], onDelete: Cascade)
taskType CollectionsCallTaskType
assignedAdminId String
assignedAdmin AdminUser @relation("CollectionsTaskAssignee", fields: [assignedAdminId], references: [id])
billingContactId String?
billingContact BillingContact? @relation(fields: [billingContactId], references: [id], onDelete: SetNull)
dueAt DateTime
status CollectionsCallTaskStatus @default(OPEN)
outcome CollectionsCallOutcome?
note String?
promisedPaymentAt DateTime?
nextFollowUpAt DateTime?
completedByAdminId String?
completedByAdmin AdminUser? @relation("CollectionsTaskCompleter", fields: [completedByAdminId], references: [id], onDelete: SetNull)
completedAt DateTime?
cancellationReason String?
companyDefaultLocale String
contactLocale String
customerScript String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([collectionsCaseId, taskType])
@@index([assignedAdminId, status, dueAt])
@@map("collections_call_tasks")
}
model CollectionsOverride {
id String @id @default(cuid())
collectionsCaseId String
collectionsCase CollectionsCase @relation(fields: [collectionsCaseId], references: [id], onDelete: Cascade)
type CollectionsOverrideType
status CollectionsOverrideStatus @default(ACTIVE)
reason String
expiresAt DateTime
revisedSuspensionAt DateTime?
pauseSuspension Boolean @default(true)
pauseNotifications Boolean @default(false)
createdByAdminId String
createdByAdmin AdminUser @relation("CollectionsOverrideCreator", fields: [createdByAdminId], references: [id])
revokedByAdminId String?
revokedByAdmin AdminUser? @relation("CollectionsOverrideRevoker", fields: [revokedByAdminId], references: [id], onDelete: SetNull)
revokedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([collectionsCaseId, status, expiresAt])
@@map("collections_overrides")
}
model CollectionsEvent {
id String @id @default(cuid())
collectionsCaseId String
collectionsCase CollectionsCase @relation(fields: [collectionsCaseId], references: [id], onDelete: Cascade)
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
eventType String
idempotencyKey String
scheduledFor DateTime?
occurredAt DateTime @default(now())
actorType String @default("system")
actorId String?
payload Json @default("{}")
@@unique([collectionsCaseId, idempotencyKey])
@@index([companyId, occurredAt])
@@map("collections_events")
}
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")
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 @db.VarChar(50)
lastName String @db.VarChar(50)
email String @db.VarChar(254)
phone String? @db.VarChar(20)
passwordHash String?
passwordResetToken String? @unique
passwordResetExpiresAt DateTime?
emailVerified DateTime?
emailVerificationToken String? @unique
role EmployeeRole @default(AGENT)
preferredLanguage String @default("en")
isActive Boolean @default(true)
totpSecret String?
totpEnabled Boolean @default(false)
notifications Notification[] @relation("EmployeeNotifications")
notificationRecipients NotificationRecipient[]
notificationPreferences NotificationPreference[]
recordedRentalPayments RentalPayment[] @relation("RecordedRentalPayments")
billingContacts BillingContact[]
manualPaymentSubmissions ManualPaymentSubmission[]
manualPaymentDocuments ManualPaymentDocument[]
subscriptionUpgradeRequests SubscriptionUpgradeRequest[] @relation("SubscriptionUpgradeRequester")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([companyId, email])
@@index([companyId])
@@index([email])
@@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? @db.VarChar(17)
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?
language String @db.VarChar(2)
firstName String @db.VarChar(50)
lastName String @db.VarChar(50)
email String @db.VarChar(254)
phone String? @db.VarChar(20)
driverLicense String? @db.VarChar(30)
dateOfBirth DateTime?
nationality String? @db.VarChar(2)
address Json?
notes String?
flagged Boolean @default(false)
flagReason String?
licenseExpiry DateTime?
licenseIssuedAt DateTime?
licenseCountry String? @db.VarChar(2)
licenseImageUrl String?
licenseNumber String? @db.VarChar(30)
licenseCategory String? @db.VarChar(30)
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
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
@@unique([companyId, idempotencyKey])
@@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 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)
billingContactId String?
billingContact BillingContact? @relation(fields: [billingContactId], references: [id], onDelete: Cascade)
adminUserId String?
adminUser AdminUser? @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
readAt DateTime?
archivedAt DateTime?
deliveries NotificationDelivery[]
createdAt DateTime @default(now())
@@index([employeeId, readAt, createdAt])
@@index([renterId, readAt, createdAt])
@@index([billingContactId, readAt, createdAt])
@@index([adminUserId, 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?
lockedAt DateTime? @map("locked_at")
lockedBy String? @map("locked_by")
attempts Int @default(0)
availableAt DateTime? @map("available_at")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status, createdAt])
@@index([status, availableAt])
@@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
language String @db.VarChar(2)
firstName String @db.VarChar(50)
lastName String @db.VarChar(50)
email String? @db.VarChar(254)
phone String? @db.VarChar(20)
driverLicense String @db.VarChar(30)
licenseExpiry DateTime?
licenseIssuedAt DateTime?
dateOfBirth DateTime?
nationality String? @db.VarChar(2)
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[]
preferredLocale String @default("en")
ownedBillingAccounts BillingAccount[] @relation("BillingAccountCollectionsOwner")
confirmedManualPayments BillingPaymentAttempt[] @relation("ManualPaymentConfirmingAdmin")
reviewedManualSubmissions ManualPaymentSubmission[] @relation("ManualPaymentReviewingAdmin")
approvedSubscriptionUpgrades SubscriptionUpgradeRequest[] @relation("SubscriptionUpgradeApprover")
ownedCollectionsCases CollectionsCase[] @relation("CollectionsCaseOwner")
assignedCollectionsTasks CollectionsCallTask[] @relation("CollectionsTaskAssignee")
completedCollectionsTasks CollectionsCallTask[] @relation("CollectionsTaskCompleter")
createdCollectionsOverrides CollectionsOverride[] @relation("CollectionsOverrideCreator")
revokedCollectionsOverrides CollectionsOverride[] @relation("CollectionsOverrideRevoker")
notificationRecipients NotificationRecipient[]
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 PlatformBillingSettings {
id String @id @default("default")
taxRate Float @default(20)
updatedAt DateTime @updatedAt
updatedBy String?
@@map("platform_billing_settings")
}
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")
}