fe8ffbeb9f
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 48s
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
2624 lines
90 KiB
Plaintext
2624 lines
90 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?
|
|
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
|
|
lastName String
|
|
email String
|
|
phone String?
|
|
passwordHash String?
|
|
passwordResetToken String? @unique
|
|
passwordResetExpiresAt DateTime?
|
|
emailVerified DateTime?
|
|
emailVerificationToken String? @unique
|
|
role EmployeeRole @default(AGENT)
|
|
preferredLanguage String @default("en")
|
|
isActive Boolean @default(true)
|
|
|
|
notifications Notification[] @relation("EmployeeNotifications")
|
|
notificationRecipients NotificationRecipient[]
|
|
notificationPreferences NotificationPreference[]
|
|
recordedRentalPayments RentalPayment[] @relation("RecordedRentalPayments")
|
|
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?
|
|
category VehicleCategory @default(ECONOMY)
|
|
seats Int @default(5)
|
|
transmission Transmission @default(AUTOMATIC)
|
|
fuelType FuelType @default(GASOLINE)
|
|
features String[]
|
|
dailyRate Int
|
|
status VehicleStatus @default(AVAILABLE)
|
|
photos String[]
|
|
mileage Int?
|
|
notes String?
|
|
isPublished Boolean @default(true)
|
|
pickupLocations String[] @default([])
|
|
allowDifferentDropoff Boolean @default(false)
|
|
dropoffLocations String[] @default([])
|
|
|
|
reservations Reservation[]
|
|
maintenance MaintenanceLog[]
|
|
offerVehicles OfferVehicle[]
|
|
calendarBlocks VehicleCalendarBlock[]
|
|
pricingConfiguration VehiclePricingConfiguration?
|
|
priceHistory VehiclePriceHistory[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([companyId])
|
|
@@index([companyId, status])
|
|
@@index([companyId, isPublished])
|
|
@@map("vehicles")
|
|
}
|
|
|
|
model VehiclePricingConfiguration {
|
|
id String @id @default(cuid())
|
|
vehicleId String @unique
|
|
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
|
pricingMode VehiclePricingMode @default(MANUAL)
|
|
baseDailyRate Int?
|
|
weeklyRate Int?
|
|
weekendRate Int?
|
|
holidayRate Int?
|
|
monthlyRate Int?
|
|
longTermDailyRate Int?
|
|
minimumDailyRate Int?
|
|
maximumDailyRate Int?
|
|
maxDailyPriceMovementPct Int @default(10)
|
|
automaticPricingEnabled Boolean @default(false)
|
|
approvalRequired Boolean @default(false)
|
|
|
|
rules VehiclePricingRule[]
|
|
history VehiclePriceHistory[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("vehicle_pricing_configurations")
|
|
}
|
|
|
|
model VehiclePricingRule {
|
|
id String @id @default(cuid())
|
|
configurationId String
|
|
configuration VehiclePricingConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
ruleType VehiclePricingRuleType @default(DATE_RANGE)
|
|
startDate DateTime
|
|
endDate DateTime
|
|
dailyRate Int?
|
|
weeklyRate Int?
|
|
weekendRate Int?
|
|
monthlyRate Int?
|
|
minDailyRate Int?
|
|
maxDailyRate Int?
|
|
automaticAdjustmentPct Int?
|
|
priceAdjustment Int?
|
|
isActive Boolean @default(true)
|
|
sortOrder Int @default(0)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([configurationId])
|
|
@@index([configurationId, startDate, endDate])
|
|
@@map("vehicle_pricing_rules")
|
|
}
|
|
|
|
model VehiclePriceHistory {
|
|
id String @id @default(cuid())
|
|
configurationId String
|
|
configuration VehiclePricingConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade)
|
|
vehicleId String
|
|
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
|
source VehiclePriceChangeSource @default(CONFIG_UPDATE)
|
|
changedByEmployeeId String?
|
|
previousDailyRate Int?
|
|
nextDailyRate Int?
|
|
previousWeeklyRate Int?
|
|
nextWeeklyRate Int?
|
|
note String?
|
|
effectiveFrom DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([vehicleId, createdAt])
|
|
@@index([configurationId, createdAt])
|
|
@@map("vehicle_price_history")
|
|
}
|
|
|
|
model Offer {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
title String
|
|
description String?
|
|
termsAndConds String?
|
|
imageUrl String?
|
|
type OfferType
|
|
discountValue Int
|
|
specialRate Int?
|
|
appliesToAll Boolean @default(true)
|
|
categories VehicleCategory[]
|
|
minRentalDays Int?
|
|
maxRentalDays Int?
|
|
promoCode String? @unique
|
|
maxRedemptions Int?
|
|
redemptionCount Int @default(0)
|
|
validFrom DateTime
|
|
validUntil DateTime
|
|
isActive Boolean @default(true)
|
|
isPublic Boolean @default(true)
|
|
isFeatured Boolean @default(false)
|
|
|
|
vehicles OfferVehicle[]
|
|
reservations Reservation[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([companyId])
|
|
@@index([isPublic, isActive, validUntil])
|
|
@@map("offers")
|
|
}
|
|
|
|
model OfferVehicle {
|
|
offerId String
|
|
offer Offer @relation(fields: [offerId], references: [id], onDelete: Cascade)
|
|
vehicleId String
|
|
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([offerId, vehicleId])
|
|
@@map("offer_vehicles")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// B2C — RENTER SIDE
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Renter {
|
|
id String @id @default(cuid())
|
|
firstName String
|
|
lastName String
|
|
email String @unique
|
|
phone String?
|
|
passwordHash String
|
|
driverLicense String?
|
|
dateOfBirth DateTime?
|
|
nationality String?
|
|
preferredLocale String @default("en")
|
|
preferredCurrency String @default("MAD")
|
|
fcmToken String?
|
|
emailVerified Boolean @default(false)
|
|
phoneVerified Boolean @default(false)
|
|
isActive Boolean @default(true)
|
|
|
|
reservations Reservation[] @relation("RenterReservations")
|
|
savedCompanies RenterSavedCompany[]
|
|
reviews Review[]
|
|
notifications Notification[] @relation("RenterNotifications")
|
|
notificationRecipients NotificationRecipient[]
|
|
notificationPreferences NotificationPreference[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("renters")
|
|
}
|
|
|
|
model RenterSavedCompany {
|
|
renterId String
|
|
renter Renter @relation(fields: [renterId], references: [id], onDelete: Cascade)
|
|
companyId String
|
|
savedAt DateTime @default(now())
|
|
|
|
@@id([renterId, companyId])
|
|
@@map("renter_saved_companies")
|
|
}
|
|
|
|
model Customer {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
renterId String?
|
|
firstName String
|
|
lastName String
|
|
email String
|
|
phone String?
|
|
driverLicense String?
|
|
dateOfBirth DateTime?
|
|
nationality String?
|
|
address Json?
|
|
notes String?
|
|
flagged Boolean @default(false)
|
|
flagReason String?
|
|
|
|
licenseExpiry DateTime?
|
|
licenseIssuedAt DateTime?
|
|
licenseCountry String?
|
|
licenseImageUrl String?
|
|
licenseNumber String?
|
|
licenseCategory String?
|
|
licenseExpired Boolean @default(false)
|
|
licenseExpiringSoon Boolean @default(false)
|
|
licenseValidationStatus LicenseStatus @default(PENDING)
|
|
licenseApprovedBy String?
|
|
licenseApprovedAt DateTime?
|
|
licenseApprovalNote String?
|
|
|
|
reservations Reservation[]
|
|
complaints Complaint[]
|
|
reviewOptOut Boolean @default(false)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([companyId, email])
|
|
@@index([companyId])
|
|
@@map("customers")
|
|
}
|
|
|
|
model Reservation {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
vehicleId String
|
|
vehicle Vehicle @relation(fields: [vehicleId], references: [id])
|
|
customerId String
|
|
customer Customer @relation(fields: [customerId], references: [id])
|
|
renterId String?
|
|
renter Renter? @relation("RenterReservations", fields: [renterId], references: [id])
|
|
offerId String?
|
|
offer Offer? @relation(fields: [offerId], references: [id])
|
|
promoCodeUsed String?
|
|
vehicleCategory VehicleCategory?
|
|
source BookingSource @default(DASHBOARD)
|
|
carplaceRef String?
|
|
status ReservationStatus @default(DRAFT)
|
|
startDate DateTime
|
|
endDate DateTime
|
|
pickupLocation String?
|
|
returnLocation String?
|
|
dailyRate Int
|
|
discountAmount Int @default(0)
|
|
totalDays Int
|
|
totalAmount Int
|
|
depositAmount Int @default(0)
|
|
paymentStatus String @default("UNPAID")
|
|
paidAmount Int @default(0)
|
|
checkedInAt DateTime?
|
|
checkedOutAt DateTime?
|
|
checkInMileage Int?
|
|
checkOutMileage Int?
|
|
notes String?
|
|
cancelReason String?
|
|
cancelledBy CancelledBy?
|
|
bookingReference String? @unique
|
|
contractNumber String? @unique
|
|
invoiceNumber String? @unique
|
|
reviewToken String? @unique
|
|
checkInFuelLevel String?
|
|
checkOutFuelLevel String?
|
|
|
|
reviewRequestSentAt DateTime?
|
|
reviewReminderSentAt DateTime?
|
|
reviewFinalReminderSentAt DateTime?
|
|
reviewPaused Boolean @default(false)
|
|
|
|
insurances ReservationInsurance[]
|
|
insuranceTotal Int @default(0)
|
|
additionalDrivers AdditionalDriver[]
|
|
additionalDriverTotal Int @default(0)
|
|
pricingRulesApplied Json?
|
|
pricingRulesTotal Int @default(0)
|
|
damageReports DamageReport[]
|
|
rentalPayments RentalPayment[]
|
|
inspections DamageInspection[]
|
|
photos ReservationPhoto[]
|
|
review Review?
|
|
complaints Complaint[]
|
|
publicAccess ReservationPublicAccess[]
|
|
damageChargeAmount Int?
|
|
damageChargeNote String?
|
|
extras Json?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([companyId])
|
|
@@index([companyId, status])
|
|
@@index([vehicleId, startDate, endDate])
|
|
@@index([renterId])
|
|
@@map("reservations")
|
|
}
|
|
|
|
model ReservationPublicAccess {
|
|
id String @id @default(cuid())
|
|
reservationId String
|
|
reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade)
|
|
tokenHash String @unique
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
revokedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([reservationId])
|
|
@@map("reservation_public_access")
|
|
}
|
|
|
|
model CarplaceFunnelEvent {
|
|
id String @id @default(cuid())
|
|
eventName String
|
|
companyId String?
|
|
companySlug String
|
|
vehicleId String
|
|
renterId String?
|
|
sessionId String?
|
|
path String?
|
|
metadata Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([eventName, createdAt])
|
|
@@index([companyId, createdAt])
|
|
@@index([companySlug, createdAt])
|
|
@@index([vehicleId, createdAt])
|
|
@@index([renterId, createdAt])
|
|
@@map("carplace_funnel_events")
|
|
}
|
|
|
|
model WebhookEvent {
|
|
id String @id @default(cuid())
|
|
provider String
|
|
providerEventId String
|
|
eventType String
|
|
status String
|
|
payloadHash String
|
|
errorMessage String?
|
|
receivedAt DateTime @default(now())
|
|
processedAt DateTime?
|
|
|
|
@@unique([provider, providerEventId])
|
|
@@index([provider, status])
|
|
@@map("webhook_events")
|
|
}
|
|
|
|
model RentalPayment {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
reservationId String
|
|
reservation Reservation @relation(fields: [reservationId], references: [id])
|
|
amount Int
|
|
currency String @default("MAD")
|
|
status PaymentStatus @default(PENDING)
|
|
type PaymentType @default(CHARGE)
|
|
paymentProvider PaymentProvider
|
|
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
|
|
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[]
|
|
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")
|
|
}
|