Files
carmanagement/docs/project-design/schema.md
T
root 8aab968e09
Build & Deploy / Build & Push Docker Image (push) Successful in 1m2s
Test / Type Check (all packages) (push) Failing after 28s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Storefront 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
Build & Deploy / Deploy to VPS (push) Successful in 3s
refactor: rename marketplace to storefront across the entire monorepo
Comprehensive rename of all marketplace references to storefront:
- API module: apps/api/src/modules/marketplace/ → storefront/
- Components: MarketplaceHeader → StorefrontHeader, MarketplaceShell →
  StorefrontShell, MarketplaceFooter → StorefrontFooter
- Types: marketplace-homepage.ts → storefront-homepage.ts
- Test files: employee-marketplace-* → employee-storefront-*
- All source code identifiers, imports, route paths, and strings
- Documentation (docs/), CI config (.gitlab-ci.yml), scripts
- Dashboard, admin, storefront workspace references
- Prisma field names preserved (isListedOnMarketplace, marketplaceRating,
  marketplaceFunnelEvent) as they map to database schema

Validation:
- API type-check: 0 errors
- Storefront type-check: 0 errors
- Dashboard type-check: 0 errors
- Full monorepo type-check: only pre-existing admin TS18046
2026-06-28 01:14:46 -04:00

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:

  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

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.