# Prisma Schema — RentalDriveGo (Complete) > Stripe has been removed. Payments use AmanPay and PayPal. ```prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ═══════════════════════════════════════════════════════════════ // 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) // RentalDriveGo billing — company pays subscription via AmanPay or PayPal // No third-party customer ID needed — we manage payment references ourselves subscriptionPaymentRef String? // last AmanPay/PayPal transaction ref // Public API key apiKey String @unique @default(cuid()) subscription Subscription? brand BrandSettings? employees Employee[] vehicles Vehicle[] offers Offer[] reservations Reservation[] customers Customer[] rentalPayments RentalPayment[] subscriptionInvoices SubscriptionInvoice[] contractSettings ContractSettings? accountingSettings AccountingSettings? notifications Notification[] @relation("CompanyNotifications") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("companies") } enum CompanyStatus { PENDING TRIALING ACTIVE PAST_DUE SUSPENDED CANCELLED } // ─── Subscription ───────────────────────────────────────────── 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") // MAD, USD, EUR trialStartAt DateTime? trialEndAt DateTime? currentPeriodStart DateTime? currentPeriodEnd DateTime? cancelledAt DateTime? cancelAtPeriodEnd Boolean @default(false) invoices SubscriptionInvoice[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("subscriptions") } enum Plan { STARTER GROWTH PRO } enum BillingPeriod { MONTHLY ANNUAL } enum SubscriptionStatus { TRIALING ACTIVE PAST_DUE CANCELLED UNPAID } // ─── Subscription Invoice ────────────────────────────────────── // Records each subscription payment (AmanPay or PayPal) 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]) amount Int // in smallest unit (centimes for MAD) currency String @default("MAD") status InvoiceStatus // Payment provider reference — exactly one is set amanpayTransactionId String? @unique paypalCaptureId String? @unique paymentProvider PaymentProvider @default(AMANPAY) paidAt DateTime? createdAt DateTime @default(now()) @@index([companyId]) @@map("subscription_invoices") } enum InvoiceStatus { PENDING PAID FAILED REFUNDED } enum FuelPolicyType { FULL_TO_FULL FULL_TO_EMPTY SAME_TO_SAME PREPAID FREE } enum PaymentProvider { AMANPAY PAYPAL } // ─── Brand Settings ─────────────────────────────────────────── 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? // Public contact info publicEmail String? publicPhone String? publicAddress String? publicCity String? publicCountry String? websiteUrl String? whatsappNumber String? facebookUrl String? instagramUrl String? // Locale + currency for public site defaultLocale String @default("en") defaultCurrency String @default("MAD") // Company's own payment accounts (for receiving rental payments) amanpayMerchantId String? // company's AmanPay merchant ID amanpaySecretKey String? // encrypted at rest paypalEmail String? // company's PayPal business email paypalMerchantId String? // optional PayPal merchant ID paymentMethodsEnabled PaymentProvider[] // which methods they have set up // Marketplace isListedOnMarketplace Boolean @default(true) marketplaceRating Float? updatedAt DateTime @updatedAt @@map("brand_settings") } // ─── Employee ───────────────────────────────────────────────── 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? role EmployeeRole @default(AGENT) isActive Boolean @default(true) notifications Notification[] @relation("EmployeeNotifications") notificationPreferences NotificationPreference[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@map("employees") } enum EmployeeRole { OWNER MANAGER AGENT } // ─── Vehicle ────────────────────────────────────────────────── // Strictly company-scoped. Photos uploaded by the company appear // on their public site AND on the global marketplace. 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[] // ["AC", "GPS", "Bluetooth", "Baby seat"] dailyRate Int // in smallest currency unit status VehicleStatus @default(AVAILABLE) // Photos uploaded via Cloudinary — these URLs are shown on the marketplace photos String[] // Cloudinary HTTPS URLs, first photo is the "cover" mileage Int? notes String? isPublished Boolean @default(true) // appears on public site + marketplace reservations Reservation[] maintenance MaintenanceLog[] offerVehicles OfferVehicle[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([companyId, status]) @@index([companyId, isPublished]) @@map("vehicles") } enum VehicleStatus { AVAILABLE RENTED MAINTENANCE OUT_OF_SERVICE } enum VehicleCategory { ECONOMY COMPACT MIDSIZE FULLSIZE SUV LUXURY VAN TRUCK } enum Transmission { AUTOMATIC MANUAL } enum FuelType { GASOLINE DIESEL ELECTRIC HYBRID } // ─── Offer ──────────────────────────────────────────────────── 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? // offer banner (Cloudinary) 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) // shown on global marketplace isFeatured Boolean @default(false) // featured carousel (GROWTH+ plan) vehicles OfferVehicle[] reservations Reservation[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([isPublic, isActive, validUntil]) @@map("offers") } enum OfferType { PERCENTAGE FIXED_AMOUNT FREE_DAY SPECIAL_RATE } model OfferVehicle { offerId String offer Offer @relation(fields: [offerId], references: [id], onDelete: Cascade) vehicleId String vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) @@id([offerId, vehicleId]) @@map("offer_vehicles") } // ═══════════════════════════════════════════════════════════════ // B2C — RENTER SIDE // ═══════════════════════════════════════════════════════════════ model Renter { id String @id @default(cuid()) firstName String lastName String email String @unique phone String? passwordHash String driverLicense String? dateOfBirth DateTime? nationality String? preferredLocale String @default("en") preferredCurrency String @default("MAD") fcmToken String? emailVerified Boolean @default(false) phoneVerified Boolean @default(false) isActive Boolean @default(true) reservations Reservation[] @relation("RenterReservations") savedCompanies RenterSavedCompany[] reviews Review[] notifications Notification[] @relation("RenterNotifications") notificationPreferences NotificationPreference[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("renters") } model RenterSavedCompany { renterId String renter Renter @relation(fields: [renterId], references: [id], onDelete: Cascade) companyId String savedAt DateTime @default(now()) @@id([renterId, companyId]) @@map("renter_saved_companies") } // Customer = company-scoped CRM record. Auto-created when someone books with a company. model Customer { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) renterId String? // linked to global Renter (if booked as logged-in renter) firstName String lastName String email String phone String? driverLicense String? dateOfBirth DateTime? nationality String? address Json? notes String? flagged Boolean @default(false) flagReason String? // ── Driver license details & validation ─────────────────── licenseExpiry DateTime? licenseIssuedAt DateTime? licenseCountry String? licenseNumber String? licenseCategory String? // "B", "A", "BE", etc. licenseExpired Boolean @default(false) licenseExpiringSoon Boolean @default(false) // <3 months validity licenseValidationStatus LicenseStatus @default(PENDING) licenseApprovedBy String? // Employee name licenseApprovedAt DateTime? licenseApprovalNote String? reservations Reservation[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([companyId, email]) @@index([companyId]) @@map("customers") } // ─── Reservation ────────────────────────────────────────────── 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? // global renter account renter Renter? @relation("RenterReservations", fields: [renterId], references: [id]) offerId String? offer Offer? @relation(fields: [offerId], references: [id]) promoCodeUsed String? // Where the booking originated source BookingSource @default(DASHBOARD) // For marketplace bookings: renter selected vehicle on explore, was redirected here marketplaceRef String? // vehicle ID + dates encoded from marketplace redirect 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) checkedInAt DateTime? checkedOutAt DateTime? checkInMileage Int? checkOutMileage Int? notes String? cancelReason String? cancelledBy CancelledBy? // Document reference numbers — assigned once, stored permanently // PDFs are NEVER stored — generated fresh from DB data on every request contractNumber String? @unique // e.g. "CNT-2024-00042" invoiceNumber String? @unique // e.g. "INV-2024-00042" // Additional check-in data needed for contract // check-in fuel moved to DamageReport.fuelLevel — kept for backward compat checkInFuelLevel String? checkOutFuelLevel String? // ── Insurance, additional driver, pricing, damage ───────── insurances ReservationInsurance[] insuranceTotal Int @default(0) additionalDrivers AdditionalDriver[] additionalDriverTotal Int @default(0) pricingRulesApplied Json? // [{ruleId, name, type, amount}] snapshot at booking pricingRulesTotal Int @default(0) damageReports DamageReport[] rentalPayments RentalPayment[] inspections DamageInspection[] review Review? // Damage charge (added by employee after checkout inspection) damageChargeAmount Int? damageChargeNote String? // Extras (GPS, baby seat, insurance, extra driver, etc.) — JSON array extras Json? // ReservationExtra[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([companyId, status]) @@index([vehicleId, startDate, endDate]) @@index([renterId]) @@map("reservations") } enum ReservationStatus { DRAFT CONFIRMED ACTIVE COMPLETED CANCELLED NO_SHOW } enum BookingSource { DASHBOARD PUBLIC_SITE MARKETPLACE API } 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 AdminRole { SUPER_ADMIN ADMIN SUPPORT FINANCE VIEWER } // ─── Rental Payment ─────────────────────────────────────────── // Payment from renter to company. No Stripe — uses AmanPay or PayPal. 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 // in smallest unit currency String @default("MAD") status PaymentStatus @default(PENDING) type PaymentType @default(CHARGE) paymentProvider PaymentProvider // Exactly one of these is set: amanpayTransactionId String? @unique paypalCaptureId String? @unique paymentMethod String? // "card_national", "card_international", "cash", "ewallet", "paypal" paidAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@index([reservationId]) @@map("rental_payments") } enum PaymentStatus { PENDING SUCCEEDED FAILED REFUNDED PARTIALLY_REFUNDED } enum PaymentType { CHARGE DEPOSIT REFUND } // ─── Review ─────────────────────────────────────────────────── 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? createdAt DateTime @default(now()) @@index([companyId]) @@map("reviews") } // ═══════════════════════════════════════════════════════════════ // 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 status NotificationStatus @default(PENDING) sentAt DateTime? failReason String? readAt DateTime? providerMessageId String? createdAt DateTime @default(now()) @@index([companyId]) @@index([renterId]) @@map("notifications") } enum NotificationType { NEW_BOOKING BOOKING_CANCELLED PAYMENT_RECEIVED PAYMENT_FAILED SUBSCRIPTION_TRIAL_ENDING SUBSCRIPTION_SUSPENDED VEHICLE_MAINTENANCE_DUE OFFER_EXPIRING NEW_REVIEW_RECEIVED BOOKING_CONFIRMED PICKUP_REMINDER_24H PICKUP_REMINDER_2H VEHICLE_READY RETURN_REMINDER BOOKING_CANCELLED_BY_COMPANY REFUND_PROCESSED NEW_OFFER_FROM_SAVED_COMPANY REVIEW_REQUEST } enum NotificationChannel { EMAIL SMS WHATSAPP IN_APP PUSH } enum NotificationStatus { PENDING SENT DELIVERED FAILED READ } model NotificationPreference { id String @id @default(cuid()) employeeId String? employee Employee? @relation(fields: [employeeId], references: [id], onDelete: Cascade) renterId String? renter Renter? @relation(fields: [renterId], references: [id], onDelete: Cascade) notificationType NotificationType channel NotificationChannel enabled Boolean @default(true) @@unique([employeeId, notificationType, channel]) @@unique([renterId, notificationType, channel]) @@map("notification_preferences") } // ─── Maintenance Log ────────────────────────────────────────── 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") } // ═══════════════════════════════════════════════════════════════ // DOCUMENTS — Rental Contracts & Customer Invoices // ═══════════════════════════════════════════════════════════════ // PDFs are NEVER stored. All fields here are DATA, not files. // contractNumber and invoiceNumber are assigned once and immutable. // ─── Contract Settings ──────────────────────────────────────── // Per-company contract customization. One record per company. // Auto-created with defaults if not yet configured. model ContractSettings { id String @id @default(cuid()) companyId String @unique company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) // Legal identity (may differ from brand display name) legalName String? registrationNumber String? // RC / SIRET / ICE taxId String? // TVA / ICE number // Contract text (company writes their own) terms String @default("") fuelPolicy String @default("The vehicle must be returned with the same fuel level as at pickup.") depositPolicy String @default("The security deposit is refundable within 7 days after return, subject to vehicle inspection.") lateFeePolicy String @default("Late returns will incur a charge of 1 additional day rate per hour of delay.") damagePolicy String @default("The renter is liable for any damage not covered by insurance.") additionalClauses String[] @default([]) // Contract options signatureRequired Boolean @default(true) contractFooterNote String? // Invoice options invoiceFooterNote String? showTax Boolean @default(false) taxRate Float? // 0.20 = 20% taxLabel String? // "TVA" | "VAT" | "Tax" // Sequential numbering — incremented atomically via DB transaction contractPrefix String @default("CNT") // CNT-2024-00001 invoicePrefix String @default("INV") // INV-2024-00001 // Structured fuel policy fuelPolicyType FuelPolicyType @default(FULL_TO_FULL) fuelPolicyNote String? fuelChargePerLiter Int? // in cents per liter fuelShortfallFee Int? // flat fee in cents if not returned full lateFeePerHour Int? // in cents per hour of late return // Multiple tax lines as JSON: [{ label, rate, appliesToCategories? }] taxLines Json? lastContractSeq Int @default(0) // NEVER reset via API lastInvoiceSeq Int @default(0) // NEVER reset via API // Additional driver pricing additionalDriverCharge AdditionalDriverCharge @default(FREE) additionalDriverDailyRate Int @default(0) // in smallest unit additionalDriverFlatRate Int @default(0) updatedAt DateTime @updatedAt @@map("contract_settings") } // ─── Insurance Policy ───────────────────────────────────────── 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 // per-day rate, flat fee, or percentage (0-100) 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]) // Snapshot at booking time policyName String policyType InsuranceType chargeType InsuranceChargeType chargeValue Int totalCharge Int @@unique([reservationId, insurancePolicyId]) @@map("reservation_insurances") } // ─── Additional Driver ──────────────────────────────────────── 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 Report ──────────────────────────────────────────── model DamageReport { id String @id @default(cuid()) reservationId String reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade) companyId String type DamageReportType damages Json // DamageZone[] — see advanced-features.md photos String[] // Cloudinary URLs (optional damage photos) fuelLevel FuelLevel @default(FULL) mileage Int? inspectedAt DateTime @default(now()) inspectedBy String? customerSignedAt DateTime? customerName String? @@unique([reservationId, type]) @@index([companyId]) @@map("damage_reports") } // ─── Pricing Rule ───────────────────────────────────────────── 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 // age or years threshold adjustmentType PriceAdjustmentType adjustmentValue Int // percentage (0-100) or flat amount in smallest unit isActive Boolean @default(true) description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId]) @@map("pricing_rules") } // ═══════════════════════════════════════════════════════════════ // DAMAGE INSPECTION SYSTEM // ═══════════════════════════════════════════════════════════════ 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? // in cents (calculated at checkout) generalCondition String? employeeNotes String? customerAgreed Boolean @default(false) inspectedAt DateTime @default(now()) inspectedBy String? // employeeId damagePoints DamagePoint[] createdAt DateTime @default(now()) @@unique([reservationId, type]) @@index([reservationId]) @@index([companyId]) @@map("damage_inspections") } enum InspectionType { CHECKIN CHECKOUT } enum FuelLevel { FULL SEVEN_EIGHTHS THREE_QUARTERS FIVE_EIGHTHS HALF THREE_EIGHTHS QUARTER ONE_EIGHTH EMPTY } model DamagePoint { id String @id @default(cuid()) inspectionId String inspection DamageInspection @relation(fields: [inspectionId], references: [id], onDelete: Cascade) viewType DiagramView x Float // 0-100 percentage of diagram width y Float // 0-100 percentage of diagram height damageType DamageType severity DamageSeverity @default(MINOR) description String? isPreExisting Boolean @default(false) @@index([inspectionId]) @@map("damage_points") } enum DiagramView { TOP FRONT REAR LEFT RIGHT } enum DamageType { SCRATCH DENT CRACK CHIP MISSING STAIN OTHER } enum DamageSeverity { MINOR MODERATE MAJOR } // ═══════════════════════════════════════════════════════════════ // ACCOUNTING SETTINGS // ═══════════════════════════════════════════════════════════════ 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) // 1=Jan currency String @default("MAD") accountantEmail String? accountantName String? autoSendReport Boolean @default(false) reportFormat ReportFormat @default(PDF) updatedAt DateTime @updatedAt @@map("accounting_settings") } enum ReportingPeriod { WEEKLY MONTHLY QUARTERLY ANNUAL } enum ReportFormat { PDF CSV BOTH } // ═══════════════════════════════════════════════════════════════ // 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? auditLogs AuditLog[] permissions AdminPermission[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("admin_users") } enum AdminRole { SUPER_ADMIN ADMIN SUPPORT FINANCE VIEWER } 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") } enum AdminResource { COMPANIES COMPANY_EMPLOYEES SUBSCRIPTIONS PAYMENTS OFFERS RENTERS ADMIN_USERS AUDIT_LOGS PLATFORM_METRICS } enum AdminAction { READ CREATE UPDATE DELETE SUSPEND IMPERSONATE } 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") } ```