# Database Schema and Table Relationships This document explains the current Prisma schema, how the data is partitioned, and how the main tables relate to each other. Source of truth: - `packages/database/prisma/schema.prisma` ## Design Principles The schema is built around a multi-tenant SaaS model with three distinct concerns: 1. company operations for running a rental business 2. renter-facing booking and review flows 3. platform-level SaaS billing and administration Important design choices: - `Company` is the tenant root for nearly every business table. - operational data and platform billing data are kept separate. - reservation-time snapshots are stored explicitly so later changes to policies or prices do not rewrite history. - some user concepts intentionally exist twice: - `Renter` is a cross-company identity - `Customer` is a company-owned CRM record - status changes are modeled with enums rather than free-text fields where the workflow matters. ## High-Level Domain Map ```mermaid erDiagram Company ||--o| Subscription : has Company ||--o| BrandSettings : has Company ||--o| ContractSettings : has Company ||--o| AccountingSettings : has Company ||--o{ Employee : employs Company ||--o{ Vehicle : owns Company ||--o{ Offer : publishes Company ||--o{ Customer : manages Company ||--o{ Reservation : receives Company ||--o{ RentalPayment : collects Company ||--o{ InsurancePolicy : defines Company ||--o{ PricingRule : defines Company ||--o{ BillingAccount : bills Company ||--o{ BillingInvoice : invoices Company ||--o{ SubscriptionInvoice : tracks Company ||--o{ Notification : receives Company ||--o{ Complaint : tracks Vehicle ||--o{ Reservation : booked_in Vehicle ||--o{ MaintenanceLog : has Vehicle ||--o{ VehicleCalendarBlock : blocked_by Offer ||--o{ OfferVehicle : applies_to Vehicle ||--o{ OfferVehicle : offered_in Customer ||--o{ Reservation : books Renter ||--o{ Reservation : owns Reservation ||--o{ RentalPayment : paid_by Reservation ||--o{ ReservationInsurance : includes Reservation ||--o{ AdditionalDriver : adds Reservation ||--o{ DamageInspection : inspected_by Reservation ||--o{ DamageReport : reported_by Reservation ||--o{ ReservationPhoto : photographed_by Reservation ||--o| Review : reviewed_as Reservation ||--o{ Complaint : disputed_in InsurancePolicy ||--o{ ReservationInsurance : snapshotted_into Renter ||--o{ Review : writes Review ||--o{ Complaint : escalates_to AdminUser ||--o{ AdminPermission : has AdminUser ||--o{ AuditLog : writes ``` ## Core Tenant Root ### `Company` `Company` is the anchor row for tenant isolation. Key fields: - identity: `id`, `name`, `slug`, `email`, `phone` - public/integration: `apiKey` - SaaS state: `status`, `subscriptionPaymentRef` - address: `address` JSON Key relations: - 1:1 with `Subscription` - 1:1 with `BrandSettings` - 1:1 with `ContractSettings` - 1:1 with `AccountingSettings` - 1:N with `Employee`, `Vehicle`, `Offer`, `Customer`, `Reservation`, `RentalPayment` - 1:N with billing tables such as `BillingAccount`, `BillingInvoice`, `SubscriptionInvoice` - 1:N with `InsurancePolicy`, `PricingRule`, `Notification`, `Complaint` Important uniqueness: - `slug` - `email` - `apiKey` ## SaaS Subscription and Billing There are two billing layers in the schema: 1. legacy provider-oriented subscription records 2. richer billing-account and billing-invoice records for platform finance operations ### `Subscription` One row per company. Purpose: - current plan - billing period - lifecycle state - trial dates - renewal windows - suspension and cancellation timing Relations: - belongs to one `Company` - has many `SubscriptionInvoice` - has many `SubscriptionEvent` - can be referenced by many `BillingInvoice` - can be referenced by many `BillingEvent` ### `SubscriptionEvent` Append-only event log for the subscription lifecycle. Examples of what it represents: - trial started - checkout created - payment received - plan changed - grace period extended - subscription suspended ### `SubscriptionInvoice` Legacy or provider-facing subscription payment records. Purpose: - capture AmanPay or PayPal payment references - track amount, currency, due date, paid date, and failure/void state - optionally bridge to the richer `BillingInvoice` model via `billingInvoiceId` ### `PaymentAttempt` Legacy payment-attempt rows tied to `SubscriptionInvoice`. This is narrower than the newer billing payment tables and exists for provider-level retry/failure tracking. ### `BillingAccount` The primary finance entity for a company at the platform level. Purpose: - billing identity and address - invoice terms - default currency - tax flags - default payment method - dunning pause state - external provider customer ID Relations: - belongs to one `Company` - has many `BillingPaymentMethod` - has many `BillingInvoice` - has many `BillingPaymentIntent` - has many `BillingPaymentAttempt` - has many `BillingCreditBalance`, `BillingCreditLedgerEntry`, `BillingCreditNote`, `BillingRefund`, `BillingEvent` ### `BillingPaymentMethod` Stored billing method metadata for the company account. Examples: - card brand/last4/expiration - ACH/bank/manual invoice style billing methods One billing account can have many payment methods; one may be selected as default. ### `BillingInvoice` The main platform invoice table. Purpose: - invoice numbering and sequencing - totals and tax math - status transitions - billing snapshot fields such as billing name and email - finance/admin metadata Relations: - belongs to one `BillingAccount` - belongs to one `Company` - optionally belongs to one `Subscription` - optionally has a linked legacy `SubscriptionInvoice` - has many `BillingInvoiceLineItem` - has many `BillingPaymentIntent` - has many `BillingPaymentAttempt` - has many `BillingTaxRecord` - has many `BillingCreditNote` - has many `BillingRefund` - has many `BillingEvent` ### Supporting billing tables - `BillingInvoiceLineItem`: immutable invoice lines with type, quantity, unit amount, and optional service period. - `BillingPaymentIntent`: payment-intent level state for a billing invoice. - `BillingPaymentAttempt`: actual collection attempts against an invoice. - `BillingCreditBalance`: running credit balance per billing account and currency. - `BillingCreditLedgerEntry`: credit ledger movement history. - `BillingCreditNote`: credit documents issued against an invoice. - `BillingRefund`: refund records for billing invoices. - `BillingTaxRecord`: tax breakdown rows for an invoice. - `BillingEvent`: append-only finance/subscription/company event log. ## Branding and Company Configuration ### `BrandSettings` One-to-one with `Company`. Purpose: - public display name, tagline, logo, favicon, hero image - theme colors - subdomain and custom domain - public contact and social metadata - default locale and currency - company-owned payment provider settings for renter payments - storefront visibility and rating - homepage/menu JSON configuration Important uniqueness: - `companyId` - `subdomain` - `customDomain` ### `ContractSettings` One-to-one with `Company`. Purpose: - rental contract legal copy - invoice/footer text - fuel and late-fee policy - tax settings - numbering prefixes and running sequences - additional-driver charging policy This table is used when generating operational rental documents. ### `AccountingSettings` One-to-one with `Company`. Purpose: - reporting period - fiscal-year start month - accounting contact - automatic report settings - preferred export format ### `PricingRule` Company-owned pricing rule definitions applied to reservations. Purpose: - define surcharge or discount logic - express the rule type, condition, and adjustment These rules are copied into reservation snapshots through `Reservation.pricingRulesApplied` and `Reservation.pricingRulesTotal`. ### `InsurancePolicy` Company-defined upsell or mandatory protection products. Purpose: - define policy name and type - define flat, percent, or per-day charges - mark required or optional coverage These are also snapshotted into reservations. ## Staff, Fleet, and Commercial Catalog ### `Employee` Company staff records. Purpose: - operational user identity - role assignment - preferred language - password-reset support - activation state Implementation note: - `clerkUserId` still exists as a required unique field in the schema, but Clerk is no longer an active auth dependency in this project. - the field is currently populated with local/generated identifiers so existing schema constraints remain satisfied. Relations: - belongs to one `Company` - has many `Notification` - has many `NotificationPreference` Important uniqueness: - `clerkUserId` ### `Vehicle` The core fleet entity. Purpose: - static vehicle information - public listing content - daily rate and status - pickup/dropoff location rules Relations: - belongs to one `Company` - has many `Reservation` - has many `MaintenanceLog` - has many `VehicleCalendarBlock` - participates in many-to-many offers through `OfferVehicle` Soft deletion is implemented operationally by setting: - `status = OUT_OF_SERVICE` - `isPublished = false` ### `MaintenanceLog` One-to-many from `Vehicle`. Purpose: - maintenance type and description - cost, mileage, performed date - next due date or mileage ### `VehicleCalendarBlock` One-to-many from `Vehicle`. Purpose: - manual or maintenance blocks on the reservation calendar - date-range unavailability independent of reservations ### `Offer` Company-defined promotional offers. Purpose: - percentage/fixed/free-day/special-rate discount logic - validity window - promo code - public and featured visibility - redemption caps Relations: - belongs to one `Company` - has many `Reservation` - many-to-many with `Vehicle` through `OfferVehicle` ### `OfferVehicle` Join table between `Offer` and `Vehicle`. Composite primary key: - `(offerId, vehicleId)` ## Renter Identity, Company CRM, and Reservation Aggregate The schema intentionally separates global renter identity from company-local customer records. ### `Renter` Cross-company end-user identity. Purpose: - login identity - app preferences and verification state - push token - reservation and review linkage across companies Relations: - has many `Reservation` - has many `Review` - has many `Notification` - has many `NotificationPreference` - has many saved companies through `RenterSavedCompany` ### `RenterSavedCompany` Join table that stores renter bookmarks. Composite primary key: - `(renterId, companyId)` ### `Customer` Company-owned CRM record for a person renting from that company. Purpose: - renter contact and profile data as understood by that company - notes and flag state - driver-license metadata - approval and compliance state - company-specific reservation history Relations: - belongs to one `Company` - has many `Reservation` - has many `Complaint` Important uniqueness: - `(companyId, email)` ### `Reservation` This is the central transactional aggregate for rental operations. Core relations: - belongs to one `Company` - belongs to one `Vehicle` - belongs to one `Customer` - optionally belongs to one `Renter` - optionally belongs to one `Offer` Core business fields: - booking source - date range - pickup/return locations - pricing fields: `dailyRate`, `discountAmount`, `totalDays`, `totalAmount`, `depositAmount` - payment summary fields: `paymentStatus`, `paidAmount` - lifecycle fields: `status`, `checkedInAt`, `checkedOutAt` - cancellation fields - contract and invoice numbering - review token and review reminder timestamps - extras and pricing-rule snapshots The reservation is the parent of most rental workflow records. ### Reservation child tables - `RentalPayment`: individual payment rows for the reservation. - `ReservationInsurance`: reservation-time snapshots of selected insurance products. - `AdditionalDriver`: reservation-time records for extra drivers and approval status. - `DamageReport`: pickup/dropoff summary damage records with photos and signatures. - `DamageInspection`: structured inspection record per reservation and inspection type. - `DamagePoint`: individual annotated damage points tied to one inspection. - `ReservationPhoto`: ad hoc pickup/dropoff photo attachments. - `Review`: at most one review per reservation. - `Complaint`: operational or post-rental dispute record, optionally linked to reservation, review, and customer. This snapshot pattern is important. The reservation stores what was actually sold and reviewed at the time of booking, not just pointers to mutable company configuration. ## Reservation-Adjacent Tables in Detail ### `RentalPayment` Operational payment rows for rentals, not SaaS billing. Purpose: - amount, currency, type, status - AmanPay or PayPal provider references - paid timestamp Relations: - belongs to one `Company` - belongs to one `Reservation` ### `ReservationInsurance` Snapshot bridge between `Reservation` and `InsurancePolicy`. It copies: - policy name - policy type - charge type - charge value - total charge That prevents later edits to the base insurance policy from mutating historical reservation pricing. ### `AdditionalDriver` Reservation child rows for additional drivers. Purpose: - identity and license details - charge model - approval requirement and approval outcome - expiry flags ### `DamageReport` One row per reservation and report type. Composite uniqueness: - `(reservationId, type)` Stores: - summary damages JSON - photo URLs - fuel level - mileage - inspection metadata ### `DamageInspection` Structured pickup/dropoff inspection entity. Composite uniqueness: - `(reservationId, type)` Stores: - fuel and mileage - general condition notes - customer agreement state - inspector identity ### `DamagePoint` Granular diagram point rows tied to `DamageInspection`. Stores: - diagram view - x/y coordinates - damage type and severity - free-text description - whether the damage is pre-existing ### `ReservationPhoto` Simple photo attachments tied to a reservation with type `PICKUP` or `DROPOFF`. ### `Review` One review per reservation. Important uniqueness: - `reservationId` Purpose: - overall, vehicle, and service ratings - comment - moderation/publication flags - company reply data - optional feedback category Relations: - optionally linked to `Renter` - can have many `Complaint` ### `Complaint` Company-scoped issue-tracking table. Can be linked to: - a reservation - a review - a customer This supports both operational complaints and post-review escalations. ## Notifications and Messaging ### `Notification` A notification can target one of three audiences: - company-level - employee-level - renter-level Purpose: - delivery type - title/body/data payload - channel - locale - send/read/failure state - provider message ID ### `NotificationTemplate` Reusable message content keyed by: - template key - channel - locale - version ### `NotificationPreference` Per-recipient opt-in/out rows. The uniqueness rules are intentionally split: - unique per `(employeeId, notificationType, channel)` - unique per `(renterId, notificationType, channel)` ## Admin and Platform Governance ### `AdminUser` Platform operator account. Purpose: - role - password hash - 2FA state - login tracking - password reset support Relations: - has many `AdminPermission` - has many `AuditLog` ### `AdminPermission` Resource/action override rows for an admin user. Important uniqueness: - `(adminUserId, resource)` ### `AuditLog` Immutable log of admin-side actions. Stores: - actor - action and resource - optional company and resource IDs - before/after JSON - IP, user agent, and note ## Pricing and Platform Content Configuration ### `PricingConfig` Platform-managed plan pricing rows. Important uniqueness: - `(plan, billingPeriod)` ### `PlanFeature` Platform-managed feature labels per plan, ordered by `sortOrder`. ### `PricingPromotion` Platform-managed promotion codes for SaaS pricing. Purpose: - code and description - discount type/value - applicable plans/periods - usage limits - validity window ## Relationship Summary by Cardinality ### One-to-one - `Company` -> `Subscription` - `Company` -> `BrandSettings` - `Company` -> `ContractSettings` - `Company` -> `AccountingSettings` ### One-to-many - `Company` -> `Employee`, `Vehicle`, `Offer`, `Customer`, `Reservation`, `RentalPayment` - `Company` -> `BillingAccount`, `BillingInvoice`, `SubscriptionInvoice`, `InsurancePolicy`, `PricingRule`, `Notification`, `Complaint` - `Vehicle` -> `Reservation`, `MaintenanceLog`, `VehicleCalendarBlock` - `Reservation` -> `RentalPayment`, `ReservationInsurance`, `AdditionalDriver`, `DamageReport`, `DamageInspection`, `ReservationPhoto`, `Complaint` - `BillingAccount` -> `BillingPaymentMethod`, `BillingInvoice`, `BillingPaymentIntent`, `BillingPaymentAttempt`, `BillingCreditBalance`, `BillingCreditLedgerEntry`, `BillingCreditNote`, `BillingRefund`, `BillingEvent` ### Many-to-many through join tables - `Offer` <-> `Vehicle` through `OfferVehicle` - `Renter` <-> `Company` through `RenterSavedCompany` ## Indexing and Uniqueness Rules Worth Remembering Operationally important uniqueness constraints: - `Company.slug` - `Company.email` - `Company.apiKey` - `BrandSettings.subdomain` - `BrandSettings.customDomain` - `Employee.clerkUserId` - `Customer(companyId, email)` - `Reservation.contractNumber` - `Reservation.invoiceNumber` - `Reservation.reviewToken` - `Review.reservationId` - `OfferVehicle(offerId, vehicleId)` - `RenterSavedCompany(renterId, companyId)` - `ReservationInsurance(reservationId, insurancePolicyId)` - `DamageReport(reservationId, type)` - `DamageInspection(reservationId, type)` - `PricingConfig(plan, billingPeriod)` - `NotificationPreference` employee and renter uniqueness pairs ## Practical Reading Guide If you need to understand the schema quickly, read it in this order: 1. `Company` 2. `Subscription`, `BillingAccount`, `BillingInvoice` 3. `Employee`, `Vehicle`, `Offer` 4. `Customer`, `Renter` 5. `Reservation` and its child tables 6. `Notification*` 7. `Admin*`, `AuditLog`, `Pricing*` That order matches how the application is structured in the API: tenant first, operations second, finance/admin last.