GPS Leaders

Help

Data Model & Schema

Every table, field, relationship, and enum in the CRM database

Internal engineering documentation for the GPS Leaders CRM V2 data model.

  • Source of truth: prisma/schema.prisma
  • Database: PostgreSQL
  • Tenancy: Single-tenant — one operator org (GPS Leaders), a GPS tracking device sales business.
  • Generated client output: src/generated/prisma
  • Totals: 29 models, 26 enums.

This document describes the schema exactly as defined. All field types, nullability, defaults, and @map column names are taken directly from the schema. Conventions used below:

  • ? after a type means the field/relation is nullable / optional.
  • "Column" is the physical Postgres column name (@map). Where omitted, the column name equals the field name.
  • "Table" is the physical Postgres table name (@@map).
  • All id fields are String @id @default(uuid()) unless noted otherwise.
  • createdAt / updatedAt are DateTime with @default(now()) and @updatedAt respectively, mapped to created_at / updated_at. These are noted per model but not re-explained.

Entity-Relationship Overview

                            ┌──────────┐
                            │   User   │  (auth + attribution; AI_AGENT supported)
                            └────┬─────┘
            assignedRep / author / invitedBy / createdBy
                                 │
        ┌────────────────────────┼───────────────────────────────┐
        │                        │                               │
   ┌────▼─────┐  status enum  ┌───▼────┐  contactId (req)     ┌────▼─────┐
   │ Company  │◄──────────────│  Lead  │─────────────────────►│ Contact  │
   │ (MASTER) │  companyId?   │(pursuit│   companyId?         │          │
   └────┬─────┘               │ record)│                      └────┬─────┘
        │                     └───┬────┘                           │
        │  1─N                    │ 1─N                            │ 1─N
        │                         │                                │
  ┌─────┼──────────┐     ┌────────┼──────────┐          ┌──────────┼─────────┐
  │     │          │     │        │          │          │          │         │
addresses notes  invoices tags  tasks  sequenceRuns  smsMessages callLogs conversations
  │            devices  emailThreads  leadScore       bookings
  │            fulfillmentOrders      activities
  │
CompanyAddress ──billTo/shipTo──► Invoice ──1─N──► InvoiceLineItem
                                     │   ──1─N──► Payment
                                     │   ──1─N──► FulfillmentOrder ──1─N──► FulfillmentItem
                                     │   ──1─N──► Device                       │
                                     │                              Device / SimCard
                          ref tables: AgreementTemplate, TaxRate, Backend,
                                      Carrier, ShippingType, PaymentTerm, ContractTerm

  SequenceDefinition ──1─N──► SequenceRun ──1─N──► SequenceStepLog
                                  │      ──1─N──► Task

  Inventory:  Device ◄──1:1──► SimCard       Both linked to FulfillmentItem.

  Cross-cutting:  Activity = immutable audit log (leadId?/companyId?/contactId?/userId? all nullable)
                  TaxonomyValue = data-driven dropdown lists (industry/source/vendor/etc.)
                  Tag / LeadTag = disposition & classification tagging on leads
                  Setting / MessageTemplate / CallList / VoicemailDrop = config & inbox tooling

The Company / Lead / Contact triangle (core architecture)

This is the most important architectural concept in the schema:

  • Company is the MASTER record for any business the org deals with. Whatever the business is — a prospect, a paying customer, a referral partner — there is one Company row for it. Its status enum (CompanyStatus: LEAD, CUSTOMER, PARTNER, CONTACT, INACTIVE, DEAD) is what classifies the record. A company is not deleted when it stops being a lead; its status changes.
  • Lead is a separate pipeline-pursuit record. A Lead represents an active sales pursuit moving through the pipeline (LeadStage). It is not the business itself — it is the deal/effort. A Lead requires a Contact (contactId is non-null) and optionally references a Company (companyId is nullable, because a lead may be sourced — e.g. a Google Maps / Plusvibe import — before a Company master record is established or matched).
  • Contact is a person. A contact optionally belongs to a Company. Contacts hold the channel identity (email, phone E.164, SMS eligibility) used by communications.
  • Most operational records (Note, Task, Activity, Invoice, communications) attach to both a Lead (optional) and a Company. Company is the durable anchor; Lead is the time-bounded pursuit context.

The Activity audit trail

Activity is an append-only, immutable audit log. It has no updatedAt — rows are written, never edited. Critically, Activity.userId is nullable: this allows attribution to non-human / system actors (background jobs, sequence worker, webhooks, AI agents) where there is no User. All four subject FKs (leadId, companyId, contactId, userId) are nullable so an activity can be scoped to whatever combination of entities applies.

Data-driven taxonomy

Dropdown / classification lists (industry, lead source, vendor, cellular carrier, backend, payment interval, shipping type, payment method) are not hard-coded enums. They live in the TaxonomyValue table, keyed by TaxonomyType. Application code stores the chosen string value on records like Company.industry, Company.source, Company.vendor, Contact.source, Lead.source. This lets the operator add/reorder/deactivate options without a schema migration. (Note: invoice-related reference data — backends, carriers, shipping types, payment/contract terms, tax rates, agreement templates — uses dedicated relational tables with proper FKs, not TaxonomyValue.)


Auth

User

Purpose: A login account / actor in the CRM. Used for authentication, ownership (assigned rep), authorship (notes, templates), and attribution. The AI_AGENT role value means a "user" may be a non-human agent seat — the schema is intentionally kept open for the future org/agent-fleet direction.

Table: users

FieldTypeNullDefaultColumn
idStringnouuid()id
emailStringnoemail
passwordHashStringnopassword_hash
firstNameStringnofirst_name
lastNameStringnolast_name
roleUserRolenoOPERATORrole
phoneStringyesphone
isActiveBooleannotrueis_active
invitedByIdStringyesinvited_by_id
invitedAtDateTimeyesinvited_at
lastLoginAtDateTimeyeslast_login_at
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • invitedByUser? self-relation "UserInvited" via invitedById, onDelete: SetNull.
  • inviteesUser[] (back-relation of "UserInvited").
  • tasksTask[] (assigned tasks).
  • activitiesActivity[].
  • notesNote[] (authored).
  • messageTemplatesMessageTemplate[] (created).
  • assignedCompaniesCompany[] via relation "CompanyAssignedRep".
  • emailAccountsEmailAccount[].
  • smsGatewaysSmsGateway[].
  • conversationsConversation[].

Constraints / indexes: email is @unique.


Core CRM

Company

Purpose: The master record for a business. Every business the org touches (prospect, customer, partner, etc.) has exactly one Company. Its status field classifies what the company currently is. Companies hold firmographic data (fleet size, industry, ICP), enrichment data scraped from Google Maps (rating, review count, hero photo, place ID), and external-system links (qbCustomerId for QuickBooks, stripeCustomerId for Stripe).

Table: companies

FieldTypeNullDefaultColumn
idStringnouuid()id
nameStringnoname
normalizedNameStringnonormalized_name
websiteStringyeswebsite
phoneStringyesphone
industryStringyesindustry
icpTagStringyesicp_tag
businessLabelStringyesbusiness_label
fleetSizeIntyesfleet_size
googleRatingDecimal(2,1)yesgoogle_rating
googleReviewCountIntyesgoogle_review_count
heroPhotoUrlStringyeshero_photo_url
googleMapsUrlStringyesgoogle_maps_url
aboutDescriptionStringyesabout_description
googlePlaceIdStringyesgoogle_place_id
linkedinCompanyUrlStringyeslinkedin_company_url
ownerNameStringyesowner_name
sourceStringyessource
vendorStringyesvendor
salesNoteStringyessales_note
taxableBooleannotruetaxable
assignedRepIdStringyesassigned_rep_id
qbCustomerIdStringyesqb_customer_id
stripeCustomerIdStringyesstripe_customer_id
statusCompanyStatusnoLEADstatus
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • contactsContact[].
  • addressesCompanyAddress[].
  • leadsLead[].
  • devicesDevice[].
  • invoicesInvoice[].
  • fulfillmentOrdersFulfillmentOrder[].
  • tasksTask[].
  • activitiesActivity[].
  • notesNote[].
  • assignedRepUser? via relation "CompanyAssignedRep" (assignedRepId), onDelete: SetNull.

Constraints / indexes: googlePlaceId is @unique. Indexes on normalizedName, icpTag, status, assignedRepId.

normalizedName is a dedupe/match key (case- and punctuation-folded company name); googlePlaceId uniqueness prevents re-importing the same Google Maps business twice.

Contact

Purpose: A person associated with a business — the human you actually email, call, or text. Holds verified channel identity. Optionally belongs to a Company. A Contact is required by every Lead.

Table: contacts

FieldTypeNullDefaultColumn
idStringnouuid()id
companyIdStringyescompany_id
firstNameStringyesfirst_name
lastNameStringyeslast_name
fullNameStringyesfull_name
titleStringyestitle
emailStringyesemail
emailNormalizedStringyesemail_normalized
emailStatusStringyesemail_status
emailVerifiedAtDateTimeyesemail_verified_at
phoneStringyesphone
phoneE164Stringyesphone_e164
phoneTypeStringyesphone_type
phoneVerifiedAtDateTimeyesphone_verified_at
isSmsEligibleBooleannofalseis_sms_eligible
timezoneStringyestimezone
linkedinUrlStringyeslinkedin_url
sourceStringyessource
plusvibeLeadIdStringyesplusvibe_lead_id
notesStringyesnotes
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • companyCompany? via companyId (no explicit onDelete; defaults apply — restrict, since companyId is optional Prisma uses SetNull semantics by default for optional relations).
  • leadsLead[].
  • smsMessagesSmsMessage[].
  • callLogsCallLog[].
  • conversationsConversation[].
  • activitiesActivity[].
  • bookingsBooking[].

Constraints / indexes: emailNormalized is @unique (prevents duplicate contacts by normalized email). Indexes on companyId, phoneE164.

Many Google-Maps-sourced leads have no contact name — that is why firstName, lastName, and fullName are all nullable. plusvibeLeadId links the contact back to the Plusvibe outbound system.

Lead

Purpose: A pipeline-pursuit record — an active sales effort moving through stages. Distinct from Company (the master business record). A lead must have a Contact; it may have a Company. Tracks stage, estimated deal value, win/loss, and outbound-automation flags (Plusvibe pause/stop, DNC).

Table: leads

FieldTypeNullDefaultColumn
idStringnouuid()id
companyIdStringyescompany_id
contactIdStringnocontact_id
stageLeadStagenoNEW_LEADstage
sourceStringyessource
sourceCampaignStringyessource_campaign
estimatedUnitsIntyesestimated_units
estimatedRevenueDecimal(10,2)yesestimated_revenue
lostReasonStringyeslost_reason
wonAtDateTimeyeswon_at
lostAtDateTimeyeslost_at
plusvibePausedBooleannofalseplusvibe_paused
plusvibeStoppedBooleannofalseplusvibe_stopped
isDncBooleannofalseis_dnc
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • companyCompany? via companyId.
  • contactContact via contactId (required).
  • tagsLeadTag[].
  • tasksTask[].
  • activitiesActivity[].
  • emailThreadsEmailThread[].
  • smsMessagesSmsMessage[].
  • callLogsCallLog[].
  • conversationsConversation[].
  • sequenceRunsSequenceRun[].
  • invoicesInvoice[].
  • leadScoreLeadScore? (1:1).
  • notesNote[].
  • bookingsBooking[].

Constraints / indexes: Indexes on companyId, contactId, stage, source, createdAt.

Note

Purpose: A free-text note authored by a user against a company (and optionally a specific lead). Used for sales commentary and history.

Table: notes

FieldTypeNullDefaultColumn
idStringnouuid()id
companyIdStringnocompany_id
leadIdStringyeslead_id
authorIdStringnoauthor_id
bodyString @db.Textnobody
createdAtDateTimenonow()created_at
editedAtDateTimeyesedited_at

Relationships:

  • companyCompany via companyId, onDelete: Cascade.
  • leadLead? via leadId, onDelete: SetNull.
  • authorUser via authorId.

Constraints / indexes: Indexes on (companyId, createdAt) and (leadId, createdAt).

A note is always anchored to a company; if its lead is deleted the note survives (leadId nulled). Notes have editedAt instead of updatedAt — explicit edit tracking.

Tag

Purpose: The catalog/definition of a tag that can be applied to leads. Tags are categorized (TagCategory) — dispositions, ICP markers, gates, or custom. System tags (isSystem) are protected from deletion.

Table: tags

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
categoryTagCategorynocategory
colorStringyescolor
descriptionStringyesdescription
isSystemBooleannofalseis_system
isActiveBooleannotrueis_active
sortOrderIntno0sort_order
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: None (referenced by slug, not FK — see LeadTag.tagSlug).

Constraints / indexes: slug is @unique. Index on (category, isActive, sortOrder).

LeadTag

Purpose: The application of a tag to a specific lead (join record between Lead and Tag). Records who applied it and when, and supports soft-removal via removedAt. Applying certain disposition tags is what triggers automation sequences.

Table: lead_tags

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringnolead_id
tagSlugStringnotag_slug
appliedAtDateTimenonow()applied_at
appliedByStringno"nick"applied_by
removedAtDateTimeyesremoved_at

Relationships: leadLead via leadId.

Constraints / indexes: @@unique([leadId, tagSlug]) named unique_active_tag. Index on tagSlug.

tagSlug is a loose string reference to Tag.slug (no enforced FK). appliedBy defaults to the string "nick" — a single-tenant default.

LeadScore

Purpose: Computed scoring/health metrics for a lead (1:1 with Lead). Holds quality score, engagement score, deal-risk classification, and recency-of-contact metrics.

Table: lead_scores

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringnolead_id
qualityScoreIntno0quality_score
engagementScoreIntno0engagement_score
dealRiskDealRisknoUNKNOWNdeal_risk
lastInteractionAtDateTimeyeslast_interaction_at
daysSinceContactIntyesdays_since_contact
updatedAtDateTimeno@updatedAtupdated_at

Relationships: leadLead via leadId.

Constraints / indexes: leadId is @unique (enforces 1:1).

TaxonomyValue

Purpose: The data-driven dropdown system. One row per selectable option for a given TaxonomyType (industry, source, vendor, cellular carrier, backend, payment interval, shipping type, payment method). Lets the operator manage list values without schema migrations. Consuming records store the chosen string value (e.g. Company.industry).

Table: taxonomy_values

FieldTypeNullDefaultColumn
idStringnouuid()id
typeTaxonomyTypenotype
valueStringnovalue
sortOrderIntno0sort_order
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: None.

Constraints / indexes: @@unique([type, value]). Index on (type, active, sortOrder).

CompanyAddress

Purpose: A physical address belonging to a company — billing, shipping, or both. A company can have multiple addresses with default-billing / default-shipping flags. Invoices reference these for bill-to / ship-to.

Table: company_addresses

FieldTypeNullDefaultColumn
idStringnouuid()id
companyIdStringnocompany_id
labelStringnolabel
typeAddressTypenotype
addressLine1Stringnoaddress_line1
addressLine2Stringyesaddress_line2
cityStringnocity
stateStringnostate
zipStringnozip
countryStringno"US"country
notesStringyesnotes
isDefaultBillingBooleannofalseis_default_billing
isDefaultShippingBooleannofalseis_default_shipping
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • companyCompany via companyId, onDelete: Cascade.
  • billedInvoicesInvoice[] via relation "InvoiceBillTo".
  • shippedInvoicesInvoice[] via relation "InvoiceShipTo".

Constraints / indexes: Index on companyId.


Tasks

Task

Purpose: A to-do / follow-up item. Can be attached to a lead and/or a company, assigned to a user, scheduled (dueAt, optional Google Calendar event link), and optionally generated by a sequence step.

Table: tasks

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
companyIdStringyescompany_id
assignedToIdStringyesassigned_to_id
titleStringnotitle
descriptionStringyesdescription
taskTypeTaskTypenoGENERALtask_type
priorityTaskPrioritynoNORMALpriority
dueAtDateTimeyesdue_at
calendarEventIdStringyescalendar_event_id
durationMinIntyesduration_min
completedAtDateTimeyescompleted_at
isCompletedBooleannofalseis_completed
sequenceRunIdStringyessequence_run_id
sequenceStepNumberIntyessequence_step_number
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • leadLead? via leadId.
  • companyCompany? via companyId.
  • assignedToUser? via assignedToId.
  • sequenceRunSequenceRun? via sequenceRunId.

Constraints / indexes: @@unique([sequenceRunId, sequenceStepNumber]) named task_run_step_unique (prevents a sequence step generating its task twice). Indexes on leadId, (isCompleted, dueAt), priority.


Activity log

Activity

Purpose: The immutable audit trail of everything that happens in the CRM — stage changes, sends, payments, system events. Append-only: rows are written, never updated (note the absence of updatedAt). userId is nullable specifically to allow attribution to non-human / system actors (background jobs, the sequence worker, webhooks, AI agents). All subject FKs are nullable so an activity can be scoped to any combination of lead/company/contact.

Table: activities

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
companyIdStringyescompany_id
contactIdStringyescontact_id
userIdStringyesuser_id
activityTypeStringnoactivity_type
summaryStringnosummary
detailJsonyesdetail
occurredAtDateTimenonow()occurred_at
createdAtDateTimenonow()created_at

Relationships:

  • leadLead? via leadId.
  • companyCompany? via companyId.
  • contactContact? via contactId.
  • userUser? via userId.

Constraints / indexes: Indexes on (leadId, occurredAt), companyId, activityType.

activityType is a free-form string (not an enum) — kept open so new event kinds can be logged without a migration. detail is JSON for arbitrary structured payload. occurredAt (when the event happened) is distinct from createdAt (when the row was written) to support backfilled / imported events.


Communications

EmailAccount

Purpose: A connected mailbox the CRM sends/receives email through. Primarily Gmail via OAuth (refresh/access tokens); SMTP fields are present as a fallback. Optionally owned by a User (supports the coming per-user Gmail routing).

Table: email_accounts

FieldTypeNullDefaultColumn
idStringnouuid()id
userIdStringyesuser_id
emailAddressStringnoemail_address
displayNameStringyesdisplay_name
providerStringno"gmail"provider
gmailRefreshTokenStringyesgmail_refresh_token
gmailAccessTokenStringyesgmail_access_token
gmailTokenExpiryDateTimeyesgmail_token_expiry
smtpHostStringyessmtp_host
smtpPortIntyessmtp_port
smtpUserStringyessmtp_user
smtpPassStringyessmtp_pass
isActiveBooleannotrueis_active
lastSyncedAtDateTimeyeslast_synced_at
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • userUser? via userId, onDelete: SetNull.
  • threadsEmailThread[].

Constraints / indexes: None beyond PK.

EmailThread

Purpose: A conversation thread of emails, tied to a Gmail thread and an EmailAccount, optionally associated with a Lead.

Table: email_threads

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
emailAccountIdStringnoemail_account_id
gmailThreadIdStringyesgmail_thread_id
subjectStringyessubject
lastMessageAtDateTimeyeslast_message_at
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • leadLead? via leadId.
  • emailAccountEmailAccount via emailAccountId.
  • messagesEmailMessage[].

Constraints / indexes: Indexes on leadId, gmailThreadId.

EmailMessage

Purpose: A single email within a thread (inbound or outbound), with rendered text/HTML bodies.

Table: email_messages

FieldTypeNullDefaultColumn
idStringnouuid()id
threadIdStringnothread_id
gmailMessageIdStringyesgmail_message_id
directionMessageDirectionnodirection
fromAddressStringnofrom_address
toAddressStringnoto_address
subjectStringyessubject
bodyTextStringyesbody_text
bodyHtmlStringyesbody_html
sentAtDateTimeyessent_at
createdAtDateTimenonow()created_at

Relationships: threadEmailThread via threadId.

Constraints / indexes: Index on threadId.

SmsGateway

Purpose: An outbound SMS channel — an Android-phone SMS gateway. Holds gateway URL, credentials (encrypted password), and the sending phone number (raw and E.164). Optionally owned by a User (per-user SMS gateways are on the roadmap).

Table: sms_gateways

FieldTypeNullDefaultColumn
idStringnouuid()id
userIdStringyesuser_id
labelStringnolabel
gatewayUrlStringnogateway_url
usernameStringnousername
passwordEncryptedStringnopassword_encrypted
fromNumberStringnofrom_number
fromNumberE164Stringnofrom_number_e164
isActiveBooleannotrueis_active
lastSeenAtDateTimeyeslast_seen_at
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: userUser? via userId, onDelete: SetNull.

Constraints / indexes: Index on fromNumberE164.

SmsMessage

Purpose: A single SMS message, inbound or outbound, optionally tied to a lead and/or contact. Tracks delivery status and provider message ID.

Table: sms_messages

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
contactIdStringyescontact_id
directionMessageDirectionnodirection
fromNumberStringnofrom_number
toNumberStringnoto_number
bodyStringnobody
providerStringno"android_gateway"provider
providerMessageIdStringyesprovider_message_id
statusSmsStatusnoQUEUEDstatus
sentAtDateTimeyessent_at
createdAtDateTimenonow()created_at

Relationships:

  • leadLead? via leadId.
  • contactContact? via contactId.

Constraints / indexes: Indexes on leadId, contactId.

CallLog

Purpose: A record of a phone call — inbound or outbound — with outcome, duration, notes, optional recording URL and transcript, and a voicemail-drop flag.

Table: call_logs

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
contactIdStringyescontact_id
directionMessageDirectionnodirection
durationSecondsIntyesduration_seconds
outcomeCallOutcomenooutcome
notesStringyesnotes
recordingUrlStringyesrecording_url
transcriptStringyestranscript
voicemailDroppedBooleannofalsevoicemail_dropped
providerStringno"manual"provider
createdAtDateTimenonow()created_at

Relationships:

  • leadLead? via leadId.
  • contactContact? via contactId.

Constraints / indexes: Index on leadId.

Conversation

Purpose: A unified-inbox thread — one per lead per channel (SMS / EMAIL / CALL). Surfaces the inbox UI: last-message preview, unread count, archive state. Optionally assigned to a User.

Table: conversations

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
contactIdStringyescontact_id
userIdStringyesuser_id
channelConversationChannelnochannel
lastMessageAtDateTimeyeslast_message_at
lastMessagePreviewStringyeslast_message_preview
unreadCountIntno0unread_count
isArchivedBooleannofalseis_archived
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • leadLead? via leadId.
  • contactContact? via contactId.
  • userUser? via userId, onDelete: SetNull.

Constraints / indexes: @@unique([leadId, channel]) (one conversation per lead per channel). Indexes on lastMessageAt (descending), isArchived, userId.

Booking

Purpose: A scheduled meeting, synced from Cal.com (self-hosted at book.trygpsleaders.com). Stores the Cal booking UID, attendee, event type, time window, status, and meeting/reschedule/cancel URLs, plus the raw webhook payload.

Table: bookings

FieldTypeNullDefaultColumn
idStringnouuid()id
calBookingUidStringnocal_booking_uid
leadIdStringyeslead_id
contactIdStringyescontact_id
attendeeEmailStringnoattendee_email
attendeeNameStringyesattendee_name
eventTypeSlugStringnoevent_type_slug
eventTypeTitleStringnoevent_type_title
startAtDateTimenostart_at
endAtDateTimenoend_at
statusBookingStatusnoSCHEDULEDstatus
meetingUrlStringyesmeeting_url
rescheduleUrlStringyesreschedule_url
cancelUrlStringyescancel_url
cancelledAtDateTimeyescancelled_at
cancelReasonStringyescancel_reason
rawPayloadJsonyesraw_payload
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • leadLead? via leadId, onDelete: SetNull.
  • contactContact? via contactId, onDelete: SetNull.

Constraints / indexes: calBookingUid is @unique. Indexes on (leadId, startAt), contactId, attendeeEmail, startAt.

CallList

Purpose: A saved power-dialer call list — a named, ordered set of leads to call through. Stores a snapshot of lead IDs and optionally the filter criteria used to build it.

Table: call_lists

FieldTypeNullDefaultColumn
idStringnouuid()id
nameStringnoname
filterCriteriaJsonyesfilter_criteria
leadIdsString[]no— (array)lead_ids
positionIntno0position
isActiveBooleannotrueis_active
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: None (leadIds is a plain string array, not an FK relation).

Constraints / indexes: None beyond PK.

VoicemailDrop

Purpose: A pre-recorded voicemail audio clip that can be "dropped" during the power-dialer flow.

Table: voicemail_drops

FieldTypeNullDefaultColumn
idStringnouuid()id
nameStringnoname
audioUrlStringnoaudio_url
durationSecondsIntnoduration_seconds
isDefaultBooleannofalseis_default
createdAtDateTimenonow()created_at

Relationships: None.

Constraints / indexes: None beyond PK.


Sequences

Disposition-triggered automation: applying a disposition tag to a lead starts a SequenceRun of the matching SequenceDefinition; a background worker advances runs step by step.

SequenceDefinition

Purpose: The template for an automation sequence. Keyed by tagSlug — the disposition tag that triggers it. The ordered steps are stored as a JSON array.

Table: sequence_definitions

FieldTypeNullDefaultColumn
idStringnouuid()id
tagSlugStringnotag_slug
nameStringnoname
descriptionStringyesdescription
stepsJsonnosteps
isActiveBooleannotrueis_active
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: runsSequenceRun[].

Constraints / indexes: tagSlug is @unique (one definition per trigger tag).

SequenceRun

Purpose: A live instance of a sequence executing against one lead. Tracks current step, the worker's next-fire time (nextStepAt), and lifecycle state (active / paused / completed / canceled) with reasons.

Table: sequence_runs

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringnolead_id
sequenceDefIdStringnosequence_def_id
tagSlugStringnotag_slug
statusSequenceStatusnoACTIVEstatus
currentStepIntno0current_step
startedAtDateTimenonow()started_at
nextStepAtDateTimeyesnext_step_at
completedAtDateTimeyescompleted_at
pausedAtDateTimeyespaused_at
pausedReasonStringyespaused_reason
canceledAtDateTimeyescanceled_at
canceledReasonStringyescanceled_reason
createdAtDateTimenonow()created_at

Relationships:

  • leadLead via leadId.
  • sequenceDefSequenceDefinition via sequenceDefId.
  • stepLogsSequenceStepLog[].
  • tasksTask[].

Constraints / indexes: Indexes on (leadId, status) and nextStepAt (the worker polls by nextStepAt).

SequenceStepLog

Purpose: A log of each step within a run — when it was scheduled, when it fired, and any error. Provides the per-step audit/diagnostics for the sequence engine.

Table: sequence_step_logs

FieldTypeNullDefaultColumn
idStringnouuid()id
sequenceRunIdStringnosequence_run_id
stepNumberIntnostep_number
actionTypeStringnoaction_type
statusStepStatusnoPENDINGstatus
scheduledAtDateTimeyesscheduled_at
firedAtDateTimeyesfired_at
errorMessageStringyeserror_message
createdAtDateTimenonow()created_at

Relationships: sequenceRunSequenceRun via sequenceRunId.

Constraints / indexes: Index on sequenceRunId.


Financial

Invoicing with QuickBooks (qb*Id fields) and Stripe (stripe*Id fields) synchronization. Invoices reference several dedicated relational reference tables.

Invoice

Purpose: A customer invoice. Always belongs to a Company (the master record); optionally tied to a Lead. Carries pricing totals, e-signature capture for the agreement, a public payment token, status lifecycle, and external IDs for Stripe and QuickBooks.

Table: invoices

FieldTypeNullDefaultColumn
idStringnouuid()id
leadIdStringyeslead_id
companyIdStringnocompany_id
billToAddressIdStringyesbill_to_address_id
shipToAddressIdStringyesship_to_address_id
invoiceNumberStringnoinvoice_number
statusInvoiceStatusnoDRAFTstatus
agreementTemplateIdStringyesagreement_template_id
taxRateIdStringyestax_rate_id
backendIdStringyesbackend_id
carrierIdStringyescarrier_id
shippingTypeIdStringyesshipping_type_id
paymentTermIdStringyespayment_term_id
contractTermIdStringyescontract_term_id
subtotalDecimal(10,2)nosubtotal
taxAmountDecimal(10,2)no0tax_amount
totalDecimal(10,2)nototal
amountPaidDecimal(10,2)no0amount_paid
balanceDueDecimal(10,2)nobalance_due
shippingCostDecimal(10,2)yesshipping_cost
dueDateDateTime @db.Dateyesdue_date
notesStringyesnotes
paymentTokenStringyespayment_token
stripeInvoiceIdStringyesstripe_invoice_id
qbInvoiceIdStringyesqb_invoice_id
sentAtDateTimeyessent_at
paidAtDateTimeyespaid_at
voidedAtDateTimeyesvoided_at
signedNameStringyessigned_name
signedAtDateTimeyessigned_at
signedIpAddrStringyessigned_ip_addr
signedUserAgentStringyessigned_user_agent
signedAgreementSnapshotString @db.Textyessigned_agreement_snapshot
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • leadLead? via leadId.
  • companyCompany via companyId (required).
  • billToAddressCompanyAddress? relation "InvoiceBillTo" via billToAddressId, onDelete: SetNull.
  • shipToAddressCompanyAddress? relation "InvoiceShipTo" via shipToAddressId, onDelete: SetNull.
  • agreementTemplateAgreementTemplate? via agreementTemplateId.
  • taxRateTaxRate? via taxRateId.
  • backendBackend? via backendId.
  • carrierCarrier? via carrierId.
  • shippingTypeShippingType? via shippingTypeId.
  • paymentTermPaymentTerm? via paymentTermId.
  • contractTermContractTerm? via contractTermId.
  • lineItemsInvoiceLineItem[].
  • paymentsPayment[].
  • fulfillmentOrdersFulfillmentOrder[].
  • devicesDevice[].

Constraints / indexes: invoiceNumber is @unique; paymentToken is @unique. Indexes on companyId, billToAddressId, shipToAddressId, status, agreementTemplateId, taxRateId.

leadId is frequently null in production — resolve an invoice's business context via companyId (the always-present master link) rather than relying on the lead. The signed* fields capture a legally-meaningful e-signature event (name, timestamp, IP, user agent, and a frozen text snapshot of the agreement at signing time).

InvoiceLineItem

Purpose: A single billable line on an invoice.

Table: invoice_line_items

FieldTypeNullDefaultColumn
idStringnouuid()id
invoiceIdStringnoinvoice_id
descriptionStringnodescription
quantityIntnoquantity
unitPriceDecimal(10,2)nounit_price
totalDecimal(10,2)nototal
productCodeStringyesproduct_code
createdAtDateTimenonow()created_at

Relationships: invoiceInvoice via invoiceId, onDelete: Cascade.

Constraints / indexes: None beyond PK.

Payment

Purpose: A payment recorded against an invoice. Method-typed; supports Stripe-collected payments (stripePaymentId) and manually-entered payments (check/cash/wire/other). Carries an optional QuickBooks payment ID for sync.

Table: payments

FieldTypeNullDefaultColumn
idStringnouuid()id
invoiceIdStringnoinvoice_id
amountDecimal(10,2)noamount
methodPaymentMethodnomethod
stripePaymentIdStringyesstripe_payment_id
referenceStringyesreference
notesStringyesnotes
paidAtDateTimenopaid_at
qbPaymentIdStringyesqb_payment_id
createdAtDateTimenonow()created_at

Relationships: invoiceInvoice via invoiceId.

Constraints / indexes: stripePaymentId is @unique (idempotency for Stripe webhooks). Index on invoiceId.

Product

Purpose: A product/service catalog item, synced from QuickBooks (qbItemId is the link). Holds price, cost, SKU, category, and product type.

Table: products

FieldTypeNullDefaultColumn
idStringnouuid()id
qbItemIdStringnoqb_item_id
nameStringnoname
descriptionStringyesdescription
skuStringyessku
unitPriceDecimal(10,2)nounit_price
costDecimal(10,2)yescost
categoryStringyescategory
typeProductTypenoONE_TIMEtype
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at
lastSyncedAtDateTimeyeslast_synced_at

Relationships: None (referenced loosely via InvoiceLineItem.productCode).

Constraints / indexes: qbItemId is @unique. Indexes on active, sku, category.

AgreementTemplate

Purpose: A reusable terms-and-conditions / agreement body that gets attached to an invoice and snapshotted at signing. Supports {{variable}} substitution via the variables JSON.

Table: agreement_templates

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
bodyString @db.Textnobody
variablesJsonnovariables
activeBooleannotrueactive
sortOrderIntno0sort_order
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (active, sortOrder).

TaxRate

Purpose: A named tax rate (CA district, resale, out-of-state, etc.) selectable on an invoice.

Table: tax_rates

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
rateDecimal(7,5)norate
stateStringyesstate
jurisdictionStringyesjurisdiction
categoryTaxRateCategorynocategory
activeBooleannotrueactive
sortOrderIntno0sort_order
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (category, active, sortOrder).

Backend

Purpose: Reference table of GPS "backend" platforms (the tracking software backend a device runs on) selectable on an invoice.

Table: backends

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
sortOrderIntno0sort_order
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (active, sortOrder).

Carrier

Purpose: Reference table of cellular carriers selectable on an invoice (the SIM/airtime carrier for the GPS devices).

Table: carriers

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
sortOrderIntno0sort_order
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (active, sortOrder).

ShippingType

Purpose: Reference table of shipping methods selectable on an invoice, each with an optional default cost.

Table: shipping_types

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
defaultCostDecimal(10,2)yesdefault_cost
sortOrderIntno0sort_order
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (active, sortOrder).

PaymentTerm

Purpose: Reference table of payment terms (e.g. Net 30) selectable on an invoice.

Table: payment_terms

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
sortOrderIntno0sort_order
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (active, sortOrder).

ContractTerm

Purpose: Reference table of contract-length terms selectable on an invoice, with an optional months value.

Table: contract_terms

FieldTypeNullDefaultColumn
idStringnouuid()id
slugStringnoslug
nameStringnoname
monthsIntyesmonths
sortOrderIntno0sort_order
activeBooleannotrueactive
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: invoicesInvoice[].

Constraints / indexes: slug is @unique. Index on (active, sortOrder).


Inventory

GPS hardware tracking — physical devices and their SIM cards, plus the fulfillment workflow that ships them.

Device

Purpose: A physical GPS tracking device (default model GL300). Tracks lifecycle status, optional assignment to a company and an invoice, an optional 1:1 SIM card, and activation/tracking state.

Table: devices

FieldTypeNullDefaultColumn
idStringnouuid()id
serialNumberStringnoserial_number
imeiStringyesimei
modelStringno"GL300"model
statusDeviceStatusnoIN_STOCKstatus
companyIdStringyescompany_id
invoiceIdStringyesinvoice_id
simCardIdStringyessim_card_id
activationDateDateTime @db.Dateyesactivation_date
trackingActiveBooleannofalsetracking_active
notesStringyesnotes
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • companyCompany? via companyId.
  • invoiceInvoice? via invoiceId.
  • simCardSimCard? via simCardId (1:1).
  • fulfillmentItemsFulfillmentItem[].

Constraints / indexes: serialNumber is @unique; imei is @unique; simCardId is @unique (enforces 1:1 with SimCard). Indexes on status, companyId.

SimCard

Purpose: A cellular SIM card that pairs 1:1 with a Device. Tracks ICCID, phone number, carrier, and lifecycle status.

Table: sim_cards

FieldTypeNullDefaultColumn
idStringnouuid()id
iccidStringnoiccid
phoneNumberStringyesphone_number
carrierStringyescarrier
statusSimStatusnoIN_STOCKstatus
activatedAtDateTimeyesactivated_at
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • deviceDevice? (back-relation of Device.simCard; 1:1).
  • fulfillmentItemsFulfillmentItem[].

Constraints / indexes: iccid is @unique. Index on status.

FulfillmentOrder

Purpose: A shipment of devices fulfilling an invoice. Tracks status through the warehouse workflow, device count, carrier, tracking number, and ship/deliver timestamps.

Table: fulfillment_orders

FieldTypeNullDefaultColumn
idStringnouuid()id
invoiceIdStringnoinvoice_id
companyIdStringnocompany_id
statusFulfillmentStatusnoPENDINGstatus
deviceCountIntnodevice_count
trackingNumberStringyestracking_number
carrierStringyescarrier
shippedAtDateTimeyesshipped_at
deliveredAtDateTimeyesdelivered_at
notesStringyesnotes
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships:

  • invoiceInvoice via invoiceId.
  • companyCompany via companyId.
  • itemsFulfillmentItem[].

Constraints / indexes: Indexes on status, invoiceId.

FulfillmentItem

Purpose: A line within a fulfillment order — one specific device (and optionally its SIM card) being shipped.

Table: fulfillment_items

FieldTypeNullDefaultColumn
idStringnouuid()id
fulfillmentOrderIdStringnofulfillment_order_id
deviceIdStringnodevice_id
simCardIdStringyessim_card_id
createdAtDateTimenonow()created_at

Relationships:

  • fulfillmentOrderFulfillmentOrder via fulfillmentOrderId, onDelete: Cascade.
  • deviceDevice via deviceId.
  • simCardSimCard? via simCardId.

Constraints / indexes: None beyond PK.


Settings / Templates

Setting

Purpose: A generic key/value application settings store. One row per setting key; value is arbitrary JSON.

Table: settings

FieldTypeNullDefaultColumn
idStringnouuid()id
keyStringnokey
valueJsonnovalue
updatedAtDateTimeno@updatedAtupdated_at

Relationships: None.

Constraints / indexes: key is @unique. (Note: this model has no createdAt.)

MessageTemplate

Purpose: A reusable email or SMS message template. Used both as sequence content and as canned quick-replies (TemplateCategory). Optionally attributed to the user who created it.

Table: message_templates

FieldTypeNullDefaultColumn
idStringnouuid()id
nameStringnoname
slugStringnoslug
channelTemplateChannelnochannel
categoryTemplateCategorynoSEQUENCEcategory
subjectStringyessubject
bodyStringnobody
isActiveBooleannotrueis_active
createdByIdStringyescreated_by_id
createdAtDateTimenonow()created_at
updatedAtDateTimeno@updatedAtupdated_at

Relationships: createdByUser? via createdById.

Constraints / indexes: slug is @unique. Index on (category, channel, isActive).


Enum Reference

All 26 enums and their complete value sets.

UserRole

Role of a user/actor. Includes AI_AGENT so a "user" can be a non-human seat. ADMIN, REP, OPERATOR, NICK, AI_AGENT

CompanyStatus

What a company is — drives classification of the master record. LEAD, CUSTOMER, PARTNER, CONTACT, INACTIVE, DEAD

AddressType

Role of a company address. BILLING, SHIPPING, BOTH

TaxonomyType

Which data-driven dropdown list a TaxonomyValue belongs to. INDUSTRY, SOURCE, VENDOR, CELLULAR_CARRIER, BACKEND, PAYMENT_INTERVAL, SHIPPING_TYPE, PAYMENT_METHOD

TagCategory

Classification of a Tag. DISPOSITION, ICP, GATE, CUSTOM

LeadStage

Pipeline stage of a Lead. NEW_LEAD, HOT, QUALIFIED, QUOTED, BUY_PERIOD_30, BUY_PERIOD_60, BUY_PERIOD_90, WON, LOST, BOGUS

DealRisk

Deal-risk classification on a LeadScore. LOW, MEDIUM, HIGH, UNKNOWN

TaskType

Kind of a Task. CALL, EMAIL, SMS, FOLLOW_UP, GENERAL

TaskPriority

Priority of a Task. URGENT, HIGH, NORMAL, LOW

BookingStatus

State of a Booking. SCHEDULED, RESCHEDULED, CANCELLED

MessageDirection

Direction of a communication (email / SMS / call). INBOUND, OUTBOUND

SmsStatus

Delivery status of an SmsMessage. QUEUED, SENT, DELIVERED, FAILED

CallOutcome

Result of a logged call. CONNECTED, NO_ANSWER, VOICEMAIL, BUSY, WRONG_NUMBER

ConversationChannel

Channel of a unified-inbox Conversation. SMS, EMAIL, CALL

SequenceStatus

Lifecycle state of a SequenceRun. ACTIVE, PAUSED, COMPLETED, CANCELED

StepStatus

State of a SequenceStepLog entry. PENDING, FIRED, SKIPPED, FAILED

ProductType

Billing type of a Product. ONE_TIME, RECURRING, SERVICE

TaxRateCategory

Category of a TaxRate. CA_DISTRICT, RESALE, OUT_OF_STATE, OTHER_STATE, OTHER

InvoiceStatus

Lifecycle state of an Invoice. DRAFT, SENT, PARTIAL, PAID, VOID

PaymentMethod

How a Payment was made. STRIPE, CHECK, CASH, WIRE, OTHER

DeviceStatus

Lifecycle state of a GPS Device. IN_STOCK, ASSIGNED, SHIPPED, ACTIVATED, RETURNED, DEFECTIVE

SimStatus

Lifecycle state of a SimCard. IN_STOCK, ASSIGNED, ACTIVE, DEACTIVATED

FulfillmentStatus

State of a FulfillmentOrder through the warehouse workflow. PENDING, PICKING, PACKED, SHIPPED, DELIVERED, CANCELED

TemplateChannel

Channel of a MessageTemplate. EMAIL, SMS

TemplateCategory

Use of a MessageTemplate. SEQUENCE, CANNED


Cross-Cutting Notes

  • External system sync. QuickBooks links: Company.qbCustomerId, Invoice.qbInvoiceId, Payment.qbPaymentId, Product.qbItemId (unique — the catalog key). Stripe links: Company.stripeCustomerId, Invoice.stripeInvoiceId, Payment.stripePaymentId (unique — webhook idempotency).
  • Soft vs hard delete. True Cascade deletes exist only on tightly-owned children: CompanyAddress, Note, InvoiceLineItem, FulfillmentItem. SetNull is used to preserve history when a parent is removed (Note.lead, Invoice bill/ship addresses, Booking lead/contact, and userId back-references). Many models use soft-delete / soft-removal flags instead (isActive, active, LeadTag.removedAt, Conversation.isArchived).
  • onDelete defaults. Where no onDelete is specified on a required relation, Prisma defaults to Restrict/NoAction; on optional relations it defaults to SetNull. Most lead/company FKs on communication and task records are optional with no explicit clause.
  • Decimal precision. Money fields use Decimal(10,2); Company.googleRating uses Decimal(2,1); TaxRate.rate uses Decimal(7,5) for fine-grained tax percentages.
  • @db.Date fields. Invoice.dueDate and Device.activationDate are date-only (no time component).
  • JSON fields. Activity.detail, Booking.rawPayload, CallList.filterCriteria, SequenceDefinition.steps, AgreementTemplate.variables, Setting.value are all Json.
  • Roadmap-aware design. EmailAccount.userId and SmsGateway.userId are nullable and per-user — built for the coming multi-account routing (per-user Gmail accounts + per-user Android SMS gateways). UserRole.AI_AGENT and the absence of a hard single-ADMIN constraint keep the door open for the org/agent-fleet direction.