Replace storefront naming across source, tests, docs, config, and production scripts. Rename the legacy top-level app directory and Carplace component files, remove duplicate storefront startup scripts, and refresh the lockfile.
18 KiB
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:
- company operations for running a rental business
- renter-facing booking and review flows
- platform-level SaaS billing and administration
Important design choices:
Companyis 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:
Renteris a cross-company identityCustomeris a company-owned CRM record
- status changes are modeled with enums rather than free-text fields where the workflow matters.
High-Level Domain Map
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:
addressJSON
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:
slugemailapiKey
SaaS Subscription and Billing
There are two billing layers in the schema:
- legacy provider-oriented subscription records
- 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
BillingInvoicemodel viabillingInvoiceId
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
- carplace visibility and rating
- homepage/menu JSON configuration
Important uniqueness:
companyIdsubdomaincustomDomain
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:
clerkUserIdstill 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_SERVICEisPublished = 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
VehiclethroughOfferVehicle
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->SubscriptionCompany->BrandSettingsCompany->ContractSettingsCompany->AccountingSettings
One-to-many
Company->Employee,Vehicle,Offer,Customer,Reservation,RentalPaymentCompany->BillingAccount,BillingInvoice,SubscriptionInvoice,InsurancePolicy,PricingRule,Notification,ComplaintVehicle->Reservation,MaintenanceLog,VehicleCalendarBlockReservation->RentalPayment,ReservationInsurance,AdditionalDriver,DamageReport,DamageInspection,ReservationPhoto,ComplaintBillingAccount->BillingPaymentMethod,BillingInvoice,BillingPaymentIntent,BillingPaymentAttempt,BillingCreditBalance,BillingCreditLedgerEntry,BillingCreditNote,BillingRefund,BillingEvent
Many-to-many through join tables
Offer<->VehiclethroughOfferVehicleRenter<->CompanythroughRenterSavedCompany
Indexing and Uniqueness Rules Worth Remembering
Operationally important uniqueness constraints:
Company.slugCompany.emailCompany.apiKeyBrandSettings.subdomainBrandSettings.customDomainEmployee.clerkUserIdCustomer(companyId, email)Reservation.contractNumberReservation.invoiceNumberReservation.reviewTokenReview.reservationIdOfferVehicle(offerId, vehicleId)RenterSavedCompany(renterId, companyId)ReservationInsurance(reservationId, insurancePolicyId)DamageReport(reservationId, type)DamageInspection(reservationId, type)PricingConfig(plan, billingPeriod)NotificationPreferenceemployee and renter uniqueness pairs
Practical Reading Guide
If you need to understand the schema quickly, read it in this order:
CompanySubscription,BillingAccount,BillingInvoiceEmployee,Vehicle,OfferCustomer,RenterReservationand its child tablesNotification*Admin*,AuditLog,Pricing*
That order matches how the application is structured in the API: tenant first, operations second, finance/admin last.