#02 — Entity Relationship Diagram
Project: crmoffice
Last updated: 2026-05-30
This document presents the ERD via Mermaid, generated from the actual 63 migration files. Render with any Mermaid-compatible viewer (GitHub, VS Code Mermaid plugin, mermaid.live). Full DDL specification → 04-DATABASE-SCHEMA.md.
#1. Domain Map (Bird's-Eye View)
IDENTITY: users, roles, permissions, role_has_permissions, model_has_roles, model_has_permissions, personal_access_tokens, sessions, password_reset_tokens
CRM: clients, contacts, leads, lead_sources, lead_statuses, activities, notes
SALES: estimates, estimate_items, proposals, contracts, invoices, invoice_items, payments, credit_notes, credit_note_invoices, items, expenses, expense_categories
PROJECTS: projects, project_members, milestones, tasks, task_assignees, task_checklist, task_dependencies, time_entries, discussions, discussion_replies
SUPPORT: departments, tickets, ticket_replies, ticket_attachments, ticket_priorities, ticket_statuses, sla_policies, kb_categories, kb_articles, kb_article_votes
ENGAGE: calendar_events, calendar_event_invitees, goals, surveys, survey_questions, survey_responses, survey_answers, announcements, newsletter_subscribers
PLATFORM: providers, provider_credentials, webhooks, webhook_deliveries, custom_fields, custom_field_values, audit_log, settings, number_sequences, notifications, files, currencies, tax_rates
flowchart TB
subgraph IDENTITY [IDENTITY — Users & RBAC]
direction LR
Users[users]
Roles[roles]
Perms[permissions]
role_has_permissions
model_has_roles
model_has_permissions
PAT[personal_access_tokens]
Sessions[sessions]
PWRT[password_reset_tokens]
end
subgraph CRM [CRM — Clients & Leads]
direction LR
Clients[clients]
Contacts[contacts]
Leads[leads]
LeadSources[lead_sources]
LeadStatuses[lead_statuses]
Activities[activities]
Notes[notes]
end
subgraph SALES [SALES — Estimates → Invoices]
direction LR
Estimates[estimates]
EstimateItems[estimate_items]
Proposals[proposals]
Contracts[contracts]
Invoices[invoices]
InvoiceItems[invoice_items]
Payments[payments]
CreditNotes[credit_notes]
CreditNoteInv[credit_note_invoices]
Items[items]
Expenses[expenses]
ExpenseCats[expense_categories]
end
subgraph PROJECTS [PROJECTS — Planning & Tasks]
direction LR
Projects[projects]
ProjectMembers[project_members]
Milestones[milestones]
Tasks[tasks]
TaskAssignees[task_assignees]
TaskCL[task_checklist]
TaskDeps[task_dependencies]
TimeEntries[time_entries]
Discussions[discussions]
DiscReplies[discussion_replies]
end
subgraph SUPPORT [SUPPORT — Tickets & KB]
direction LR
Departments[departments]
Tickets[tickets]
TicketReplies[ticket_replies]
TicketAtts[ticket_attachments]
TicketPrio[ticket_priorities]
TicketStatus[ticket_statuses]
SLAPolicies[sla_policies]
KBCats[kb_categories]
KBArticles[kb_articles]
KBVotes[kb_article_votes]
end
subgraph ENGAGE [ENGAGEMENT — Calendar, Goals, Surveys]
direction LR
CalendarEvents[calendar_events]
CalendarInvitees[calendar_event_invitees]
Goals[goals]
Surveys[surveys]
SurveyQ[survey_questions]
SurveyR[survey_responses]
SurveyA[survey_answers]
Announcements[announcements]
NewsSubs[newsletter_subscribers]
end
subgraph PLATFORM [PLATFORM — Cross-Cutting]
direction LR
Providers[providers]
ProvCreds[provider_credentials]
Webhooks[webhooks]
WebhookDel[webhook_deliveries]
CustomFields[custom_fields]
CFValues[custom_field_values]
AuditLog[audit_log]
Settings[settings]
NumSeq[number_sequences]
Notifications[notifications]
Files[files]
Currencies[currencies]
TaxRates[tax_rates]
end
CRM --> SALES
CRM --> PROJECTS
CRM --> SUPPORT
CRM --> ENGAGE
SALES --> PROJECTS
PLATFORM -.- IDENTITY
PLATFORM -.- CRM
PLATFORM -.- SALES
PLATFORM -.- PROJECTS
PLATFORM -.- SUPPORT
PLATFORM -.- ENGAGE
#2. Identity & RBAC ERD
erDiagram
USERS ||--o{ PERSONAL_ACCESS_TOKENS : "owns"
USERS ||--o{ SESSIONS : "has"
USERS ||--o{ MODEL_HAS_ROLES : "assigned via"
USERS ||--o{ MODEL_HAS_PERMISSIONS : "assigned via"
ROLES ||--o{ MODEL_HAS_ROLES : "granted to"
ROLES ||--|{ ROLE_HAS_PERMISSIONS : "composed of"
PERMISSIONS ||--|{ ROLE_HAS_PERMISSIONS : "included in"
PERMISSIONS ||--o{ MODEL_HAS_PERMISSIONS : "directly on"
USERS ||--o| FILES : "has avatar"
USERS {
bigint id PK
string name
string email UK
timestamp email_verified_at "nullable"
string password
string remember_token
string phone "nullable"
string job_title "nullable"
decimal hourly_rate "nullable"
bigint avatar_file_id FK "nullable → files.id"
boolean is_active "default true"
text two_factor_secret "nullable"
text two_factor_recovery_codes "nullable"
timestamp last_login_at "nullable"
string last_login_ip "nullable"
string locale "default en"
string timezone "default UTC"
json notification_preferences "nullable"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
ROLES {
bigint id PK
string name
string guard_name
timestamp created_at
timestamp updated_at
}
PERMISSIONS {
bigint id PK
string name
string guard_name
timestamp created_at
timestamp updated_at
}
ROLE_HAS_PERMISSIONS {
bigint permission_id FK "→ permissions.id CASCADE"
bigint role_id FK "→ roles.id CASCADE"
}
MODEL_HAS_ROLES {
bigint role_id FK "→ roles.id CASCADE"
string model_type
bigint model_id
}
MODEL_HAS_PERMISSIONS {
bigint permission_id FK "→ permissions.id CASCADE"
string model_type
bigint model_id
}
PERSONAL_ACCESS_TOKENS {
bigint id PK
string tokenable_type
bigint tokenable_id
string name
string token UK
text abilities "nullable"
timestamp last_used_at "nullable"
timestamp expires_at "nullable"
timestamp created_at
timestamp updated_at
}
SESSIONS {
string id PK
bigint user_id FK "nullable → users.id"
string ip_address "nullable"
text user_agent "nullable"
longtext payload
int last_activity
}
#3. Core CRM ERD
erDiagram
USERS ||--o{ CLIENTS : "account_manager"
USERS ||--o{ LEADS : "assigned_to"
CLIENTS ||--o{ CONTACTS : "has contacts"
CLIENTS ||--o{ ACTIVITIES : "tracked for"
CLIENTS ||--o{ NOTES : "annotated on"
LEADS ||--o{ ACTIVITIES : "tracked for"
LEADS ||--o{ NOTES : "annotated on"
LEADS }o--|| LEAD_STATUSES : "in stage"
LEADS }o--|| LEAD_SOURCES : "came from"
LEADS ||--o| CLIENTS : "converted to"
CURRENCIES ||--o{ CLIENTS : "default_currency"
CURRENCIES ||--o{ LEADS : "currency"
USERS ||--o{ ACTIVITIES : "performed"
USERS ||--o{ NOTES : "authored"
FILES ||--o{ USERS : "avatar"
CLIENTS {
bigint id PK
string company_name "indexed"
string industry "nullable"
string website "nullable"
string phone "nullable"
text billing_address "nullable"
string billing_city "nullable"
string billing_state "nullable"
char billing_country "nullable"
string billing_postal "nullable"
text shipping_address "nullable"
string shipping_city "nullable"
string shipping_state "nullable"
char shipping_country "nullable"
string shipping_postal "nullable"
string tax_id "nullable"
bigint account_manager_id FK "nullable → users.id nullOnDelete"
bigint default_currency_id FK "→ currencies.id restrictOnDelete"
string default_language "default en"
string status "default active, indexed"
text notes "nullable"
json custom_fields "nullable"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
CONTACTS {
bigint id PK
bigint client_id FK "→ clients.id cascadeOnDelete"
string first_name
string last_name "nullable"
string email "nullable UK"
string phone "nullable"
string position "nullable"
boolean is_primary "default false"
boolean portal_access "default false"
string password "nullable"
string remember_token
string invitation_token "nullable"
timestamp invitation_expires_at "nullable"
timestamp last_login_at "nullable"
boolean receives_invoice_emails "default true"
boolean receives_ticket_emails "default true"
boolean receives_project_emails "default true"
string locale "default en"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
LEADS {
bigint id PK
string name
string company "nullable"
string email "nullable"
string phone "nullable"
string website "nullable"
text address "nullable"
string city "nullable"
char country "nullable"
decimal estimated_value "nullable"
bigint currency_id FK "nullable → currencies.id nullOnDelete"
bigint lead_source_id FK "nullable → lead_sources.id nullOnDelete"
bigint lead_status_id FK "→ lead_statuses.id restrictOnDelete"
bigint assigned_to FK "nullable → users.id nullOnDelete"
text description "nullable"
date expected_close "nullable"
timestamp converted_at "nullable"
bigint converted_to_client_id FK "nullable → clients.id nullOnDelete"
json custom_fields "nullable"
timestamp last_activity_at "nullable indexed"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
LEAD_SOURCES {
bigint id PK
string name UK
string form_token "nullable UK"
string slug "nullable UK"
boolean is_active "default true"
int order "default 0"
timestamp created_at
timestamp updated_at
}
LEAD_STATUSES {
bigint id PK
string name UK
int order "default 0"
string color "default #3b82f6"
boolean is_default "default false"
boolean is_won "default false"
boolean is_lost "default false"
timestamp created_at
timestamp updated_at
}
ACTIVITIES {
bigint id PK
string subject_type
bigint subject_id
string type
string subject "nullable"
text description "nullable"
bigint user_id FK "nullable → users.id nullOnDelete"
datetime occurred_at "indexed"
int duration_minutes "nullable"
timestamp created_at
timestamp updated_at
}
NOTES {
bigint id PK
string notable_type
bigint notable_id
text body
bigint user_id FK "nullable → users.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
#4. Sales ERD
erDiagram
CLIENTS ||--o{ ESTIMATES : "billed to"
CLIENTS ||--o{ PROPOSALS : "addressed to"
CLIENTS ||--o{ CONTRACTS : "contracted with"
CLIENTS ||--o{ INVOICES : "billed to"
CLIENTS ||--o{ CREDIT_NOTES : "credited to"
CLIENTS ||--o{ EXPENSES : "incurred for"
PROPOSALS ||--o| LEADS : "based on"
ESTIMATES ||--o{ ESTIMATE_ITEMS : "contains"
ESTIMATES ||--o| INVOICES : "converted to"
INVOICES ||--o{ INVOICE_ITEMS : "contains"
INVOICES ||--o{ PAYMENTS : "paid by"
INVOICES ||--o{ CREDIT_NOTE_INVOICES : "credited by"
CREDIT_NOTES ||--o{ CREDIT_NOTE_INVOICES : "applied to"
INVOICES ||--o{ INVOICES : "recurring child"
PROJECTS ||--o{ INVOICES : "billed for"
PROJECTS ||--o{ EXPENSES : "incurred for"
ITEMS ||--o{ ESTIMATE_ITEMS : "templated by"
ITEMS ||--o{ INVOICE_ITEMS : "templated by"
TAX_RATES ||--o{ ITEMS : "default tax"
TAX_RATES ||--o{ ESTIMATE_ITEMS : "tax"
TAX_RATES ||--o{ INVOICE_ITEMS : "tax"
TAX_RATES ||--o{ EXPENSES : "tax"
CURRENCIES ||--o{ ITEMS : "currency"
CURRENCIES ||--o{ ESTIMATES : "denominated in"
CURRENCIES ||--o{ PROPOSALS : "denominated in"
CURRENCIES ||--o{ CONTRACTS : "denominated in"
CURRENCIES ||--o{ INVOICES : "denominated in"
CURRENCIES ||--o{ PAYMENTS : "denominated in"
CURRENCIES ||--o{ CREDIT_NOTES : "denominated in"
CURRENCIES ||--o{ EXPENSES : "denominated in"
EXPENSE_CATEGORIES ||--o{ EXPENSES : "categorizes"
TIME_ENTRIES ||--o{ INVOICE_ITEMS : "billed as"
EXPENSES ||--o{ INVOICE_ITEMS : "billed as"
USERS ||--o{ ESTIMATES : "created by"
USERS ||--o{ PROPOSALS : "created by"
USERS ||--o{ CONTRACTS : "created by"
USERS ||--o{ INVOICES : "created by"
USERS ||--o{ CREDIT_NOTES : "created by"
USERS ||--o{ EXPENSES : "created by"
PROVIDERS ||--o{ PAYMENTS : "processed via"
FILES ||--o{ INVOICES : "pdf file"
FILES ||--o{ EXPENSES : "receipt"
ESTIMATES {
bigint id PK
string number UK
bigint client_id FK "→ clients.id restrictOnDelete"
date estimate_date
date expiry_date "nullable"
bigint currency_id FK "→ currencies.id restrictOnDelete"
decimal subtotal "default 0"
decimal discount_total "default 0"
decimal tax_total "default 0"
decimal total "default 0"
string status "default draft, indexed"
text notes "nullable"
text terms "nullable"
char public_token UK
bigint converted_invoice_id FK "nullable → invoices.id nullOnDelete"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
ESTIMATE_ITEMS {
bigint id PK
bigint estimate_id FK "→ estimates.id cascadeOnDelete"
bigint item_id FK "nullable → items.id nullOnDelete"
text description
decimal quantity "default 1"
decimal unit_price
bigint tax_rate_id FK "nullable → tax_rates.id nullOnDelete"
decimal discount_pct "default 0"
decimal line_total
int order "default 0"
}
PROPOSALS {
bigint id PK
string number UK
string subject
bigint client_id FK "nullable → clients.id nullOnDelete"
bigint lead_id FK "nullable → leads.id nullOnDelete"
longtext content
decimal total "default 0"
bigint currency_id FK "→ currencies.id restrictOnDelete"
date open_until "nullable"
string status "default draft"
char public_token UK
datetime accepted_at "nullable"
string accepted_by_name "nullable"
text accepted_signature "nullable"
string accepted_ip "nullable"
datetime declined_at "nullable"
text decline_reason "nullable"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
CONTRACTS {
bigint id PK
string number UK
string subject
bigint client_id FK "→ clients.id restrictOnDelete"
longtext content
date start_date
date end_date "nullable"
decimal contract_value "nullable"
bigint currency_id FK "→ currencies.id restrictOnDelete"
string status "default draft"
char public_token UK
datetime signed_at "nullable"
string signed_by_name "nullable"
text signed_signature "nullable"
string signed_ip "nullable"
int notify_expiry_days_before "default 14"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
INVOICES {
bigint id PK
string number UK
bigint client_id FK "→ clients.id restrictOnDelete"
bigint project_id FK "nullable → projects.id nullOnDelete"
bigint estimate_id FK "nullable → estimates.id nullOnDelete"
bigint recurring_parent_id FK "nullable → invoices.id nullOnDelete"
date invoice_date
date due_date "indexed"
bigint currency_id FK "→ currencies.id restrictOnDelete"
decimal subtotal "default 0"
decimal discount_total "default 0"
decimal tax_total "default 0"
decimal total "default 0"
decimal paid_total "default 0"
decimal balance_due "default 0"
string status "default draft, indexed"
boolean is_recurring "default false"
string recurring_period "nullable"
int recurring_count "nullable"
int recurring_remaining "nullable"
date next_recurring_date "nullable indexed"
text notes "nullable"
text terms "nullable"
char public_token UK
bigint pdf_file_id FK "nullable → files.id nullOnDelete"
timestamp sent_at "nullable"
timestamp viewed_at "nullable"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
INVOICE_ITEMS {
bigint id PK
bigint invoice_id FK "→ invoices.id cascadeOnDelete"
bigint item_id FK "nullable → items.id nullOnDelete"
bigint time_entry_id FK "nullable → time_entries.id nullOnDelete"
bigint expense_id FK "nullable → expenses.id nullOnDelete"
text description
decimal quantity "default 1"
decimal unit_price
bigint tax_rate_id FK "nullable → tax_rates.id nullOnDelete"
decimal discount_pct "default 0"
decimal line_total
int order "default 0"
}
PAYMENTS {
bigint id PK
bigint invoice_id FK "→ invoices.id restrictOnDelete"
decimal amount
bigint currency_id FK "→ currencies.id restrictOnDelete"
string method
bigint provider_id FK "nullable → providers.id nullOnDelete"
string transaction_id "nullable indexed"
datetime paid_at
text note "nullable"
string status "default completed"
json raw_payload "nullable"
timestamp created_at
timestamp updated_at
}
CREDIT_NOTES {
bigint id PK
string number UK
bigint client_id FK "→ clients.id restrictOnDelete"
date issue_date
decimal total
decimal applied_total "default 0"
decimal refunded_total "default 0"
bigint currency_id FK "→ currencies.id restrictOnDelete"
string status "default open"
text reason "nullable"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
CREDIT_NOTE_INVOICES {
bigint id PK
bigint credit_note_id FK "→ credit_notes.id cascadeOnDelete"
bigint invoice_id FK "→ invoices.id cascadeOnDelete"
decimal amount_applied
timestamp applied_at
}
ITEMS {
bigint id PK
string name
text description "nullable"
decimal default_price "default 0"
bigint default_tax_rate_id FK "nullable → tax_rates.id nullOnDelete"
bigint currency_id FK "nullable → currencies.id nullOnDelete"
string unit "nullable"
string sku "nullable indexed"
boolean is_active "default true"
timestamp created_at
timestamp updated_at
}
EXPENSE_CATEGORIES {
bigint id PK
string name
text description "nullable"
boolean is_active "default true"
timestamp created_at
timestamp updated_at
}
EXPENSES {
bigint id PK
bigint expense_category_id FK "nullable → expense_categories.id nullOnDelete"
bigint client_id FK "nullable → clients.id nullOnDelete"
bigint project_id FK "nullable → projects.id nullOnDelete"
string vendor "nullable"
text description
decimal amount
bigint currency_id FK "→ currencies.id restrictOnDelete"
bigint tax_rate_id FK "nullable → tax_rates.id nullOnDelete"
date expense_date
boolean is_billable "default false"
boolean is_invoiced "default false"
bigint invoice_item_id FK "nullable → invoice_items.id nullOnDelete"
bigint receipt_file_id FK "nullable → files.id nullOnDelete"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
#5. Projects & Tasks ERD
erDiagram
CLIENTS ||--o{ PROJECTS : "owns"
USERS ||--o{ PROJECTS : "manages"
PROJECTS ||--o{ PROJECT_MEMBERS : "team"
USERS ||--o{ PROJECT_MEMBERS : "member of"
PROJECTS ||--o{ MILESTONES : "phased by"
PROJECTS ||--o{ TASKS : "contains"
MILESTONES ||--o{ TASKS : "groups"
TASKS ||--o| TASKS : "parent of"
TASKS ||--o{ TASK_ASSIGNEES : "assigned to"
USERS ||--o{ TASK_ASSIGNEES : "assignee"
TASKS ||--o{ TASK_CHECKLIST : "checklist"
TASKS ||--o{ TASK_DEPENDENCIES : "depends on"
TASKS ||--o{ TASK_DEPENDENCIES : "blocks"
TASKS ||--o{ TIME_ENTRIES : "timed on"
PROJECTS ||--o{ TIME_ENTRIES : "timed on"
USERS ||--o{ TASKS : "created by"
USERS ||--o{ TIME_ENTRIES : "tracked by"
PROJECTS ||--o{ DISCUSSIONS : "discussed"
USERS ||--o{ DISCUSSIONS : "started"
DISCUSSIONS ||--o{ DISCUSSION_REPLIES : "replied"
USERS ||--o{ DISCUSSION_REPLIES : "replied"
CONTACTS ||--o{ DISCUSSION_REPLIES : "replied"
CURRENCIES ||--o{ PROJECTS : "currency"
PROJECTS {
bigint id PK
string name
text description "nullable"
bigint client_id FK "→ clients.id restrictOnDelete"
bigint project_manager_id FK "nullable → users.id nullOnDelete"
date start_date "nullable"
date deadline "nullable"
decimal estimate_hours "nullable"
string billing_method "default fixed"
decimal fixed_price "nullable"
decimal hourly_rate "nullable"
bigint currency_id FK "→ currencies.id restrictOnDelete"
string status "default not_started, indexed"
decimal progress_pct "default 0"
boolean is_visible_to_customer "default true"
json custom_fields "nullable"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
PROJECT_MEMBERS {
bigint id PK
bigint project_id FK "→ projects.id cascadeOnDelete"
bigint user_id FK "→ users.id cascadeOnDelete"
string role "nullable"
timestamp added_at
}
MILESTONES {
bigint id PK
bigint project_id FK "→ projects.id cascadeOnDelete"
string name
text description "nullable"
date due_date "nullable"
int order "default 0"
decimal complete_pct "default 0"
timestamp created_at
timestamp updated_at
}
TASKS {
bigint id PK
bigint project_id FK "nullable → projects.id cascadeOnDelete"
bigint milestone_id FK "nullable → milestones.id nullOnDelete"
bigint parent_task_id FK "nullable → tasks.id nullOnDelete"
string title
longtext description "nullable"
string priority "default medium"
string status "default todo, indexed"
date start_date "nullable"
date due_date "nullable indexed"
decimal estimate_hours "nullable"
boolean is_billable "default false"
decimal hourly_rate "nullable"
boolean is_visible_to_customer "default false"
int order "default 0"
timestamp completed_at "nullable"
bigint created_by FK "nullable → users.id nullOnDelete"
json custom_fields "nullable"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
TASK_ASSIGNEES {
bigint id PK
bigint task_id FK "→ tasks.id cascadeOnDelete"
bigint user_id FK "→ users.id cascadeOnDelete"
timestamp assigned_at
}
TASK_CHECKLIST {
bigint id PK
bigint task_id FK "→ tasks.id cascadeOnDelete"
string item
boolean is_done "default false"
int order "default 0"
timestamp done_at "nullable"
}
TASK_DEPENDENCIES {
bigint id PK
bigint task_id FK "→ tasks.id cascadeOnDelete"
bigint depends_on_task_id FK "→ tasks.id cascadeOnDelete"
string type "default finish_to_start"
}
TIME_ENTRIES {
bigint id PK
bigint task_id FK "nullable → tasks.id nullOnDelete"
bigint project_id FK "nullable → projects.id nullOnDelete"
bigint user_id FK "→ users.id cascadeOnDelete"
datetime start_at
datetime end_at "nullable"
int minutes "nullable"
decimal hourly_rate "nullable"
boolean is_billable "default false"
boolean is_invoiced "default false"
bigint invoice_item_id FK "nullable → invoice_items.id nullOnDelete"
text note "nullable"
timestamp created_at
timestamp updated_at
}
DISCUSSIONS {
bigint id PK
bigint project_id FK "→ projects.id cascadeOnDelete"
string subject
longtext body "nullable"
bigint user_id FK "nullable → users.id nullOnDelete"
boolean is_visible_to_customer "default false"
timestamp created_at
timestamp updated_at
}
DISCUSSION_REPLIES {
bigint id PK
bigint discussion_id FK "→ discussions.id cascadeOnDelete"
longtext body
bigint user_id FK "nullable → users.id nullOnDelete"
bigint contact_id FK "nullable → contacts.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
#6. Support ERD
erDiagram
DEPARTMENTS ||--o{ TICKETS : "routes to"
USERS ||--o{ DEPARTMENTS : "default assignee"
CLIENTS ||--o{ TICKETS : "opens"
CONTACTS ||--o{ TICKETS : "opens"
USERS ||--o{ TICKETS : "assigned to"
TICKET_PRIORITIES ||--|{ TICKETS : "priority of"
TICKET_STATUSES ||--|{ TICKETS : "status of"
SLA_POLICIES ||--o{ TICKETS : "sla of"
PROJECTS ||--o{ TICKETS : "related to"
TICKETS ||--o{ TICKET_REPLIES : "has replies"
USERS ||--o{ TICKET_REPLIES : "replied by"
CONTACTS ||--o{ TICKET_REPLIES : "replied by"
TICKETS ||--o{ TICKET_ATTACHMENTS : "attachments"
TICKET_REPLIES ||--o{ TICKET_ATTACHMENTS : "attachments on"
FILES ||--o{ TICKET_ATTACHMENTS : "file"
KB_CATEGORIES ||--o{ KB_CATEGORIES : "parent of"
KB_CATEGORIES ||--o{ KB_ARTICLES : "groups"
USERS ||--o{ KB_ARTICLES : "authored by"
KB_ARTICLES ||--o{ KB_ARTICLE_VOTES : "voted on"
DEPARTMENTS {
bigint id PK
string name UK
string email_pipe "nullable UK"
string inbound_token "nullable UK"
text description "nullable"
bigint default_assignee_id FK "nullable → users.id nullOnDelete"
boolean is_active "default true"
timestamp created_at
timestamp updated_at
}
TICKET_PRIORITIES {
bigint id PK
string name
int response_minutes_sla "nullable"
int resolve_minutes_sla "nullable"
string color "default #6b7280"
int order "default 0"
boolean is_active "default true"
}
TICKET_STATUSES {
bigint id PK
string name
boolean is_open "default true"
boolean is_resolved "default false"
int order "default 0"
string color "default #3b82f6"
}
SLA_POLICIES {
bigint id PK
string name
json rules
boolean is_active "default true"
timestamp created_at
timestamp updated_at
}
TICKETS {
bigint id PK
string number UK
string subject
longtext body "nullable"
bigint client_id FK "nullable → clients.id nullOnDelete"
bigint contact_id FK "nullable → contacts.id nullOnDelete"
string email_from "nullable"
bigint department_id FK "→ departments.id restrictOnDelete"
bigint priority_id FK "→ ticket_priorities.id restrictOnDelete"
bigint status_id FK "→ ticket_statuses.id restrictOnDelete"
bigint sla_policy_id FK "nullable → sla_policies.id nullOnDelete"
bigint assigned_to FK "nullable → users.id nullOnDelete"
bigint related_project_id FK "nullable → projects.id nullOnDelete"
timestamp first_response_at "nullable"
timestamp first_response_due_at "nullable indexed"
timestamp resolved_at "nullable"
timestamp resolve_due_at "nullable"
timestamp closed_at "nullable"
json custom_fields "nullable"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
TICKET_REPLIES {
bigint id PK
bigint ticket_id FK "→ tickets.id cascadeOnDelete"
longtext body
bigint user_id FK "nullable → users.id nullOnDelete"
bigint contact_id FK "nullable → contacts.id nullOnDelete"
string email_from "nullable"
boolean is_internal "default false"
string source "default web"
string email_message_id "nullable"
timestamp created_at
timestamp updated_at
}
TICKET_ATTACHMENTS {
bigint id PK
bigint ticket_id FK "→ tickets.id cascadeOnDelete"
bigint ticket_reply_id FK "nullable → ticket_replies.id cascadeOnDelete"
bigint file_id FK "→ files.id cascadeOnDelete"
timestamp created_at "nullable"
}
KB_CATEGORIES {
bigint id PK
bigint parent_id FK "nullable → kb_categories.id nullOnDelete"
string name
string slug UK
text description "nullable"
int order "default 0"
boolean is_public "default true"
timestamp created_at
timestamp updated_at
}
KB_ARTICLES {
bigint id PK
bigint category_id FK "→ kb_categories.id restrictOnDelete"
string title
string slug UK
text excerpt "nullable"
longtext content
boolean is_published "default false"
int view_count "default 0"
int helpful_count "default 0"
int unhelpful_count "default 0"
bigint author_id FK "nullable → users.id nullOnDelete"
timestamp published_at "nullable"
string meta_title "nullable"
string meta_description "nullable"
timestamp deleted_at "nullable softDeletes"
timestamp created_at
timestamp updated_at
}
KB_ARTICLE_VOTES {
bigint id PK
bigint article_id FK "→ kb_articles.id cascadeOnDelete"
string voter_ip
boolean helpful
timestamp voted_at
}
#7. Engagement ERD
erDiagram
USERS ||--o{ CALENDAR_EVENTS : "owns"
CALENDAR_EVENTS ||--o{ CALENDAR_EVENT_INVITEES : "invites"
USERS ||--o{ CALENDAR_EVENT_INVITEES : "invited"
USERS ||--o{ GOALS : "assigned to"
USERS ||--o{ SURVEYS : "created by"
SURVEYS ||--o{ SURVEY_QUESTIONS : "has"
SURVEYS ||--o{ SURVEY_RESPONSES : "responses"
CONTACTS ||--o{ SURVEY_RESPONSES : "submitted by"
SURVEY_RESPONSES ||--o{ SURVEY_ANSWERS : "answers"
SURVEY_QUESTIONS ||--o{ SURVEY_ANSWERS : "answered"
USERS ||--o{ ANNOUNCEMENTS : "authored"
CALENDAR_EVENTS {
bigint id PK
bigint user_id FK "→ users.id cascadeOnDelete"
string title
text description "nullable"
datetime starts_at "indexed"
datetime ends_at "nullable"
boolean all_day "default false"
string color "default #3b82f6"
string related_type "nullable"
bigint related_id "nullable"
int reminder_minutes_before "nullable"
timestamp created_at
timestamp updated_at
}
CALENDAR_EVENT_INVITEES {
bigint id PK
bigint event_id FK "→ calendar_events.id cascadeOnDelete"
bigint user_id FK "→ users.id cascadeOnDelete"
string response "default pending"
}
GOALS {
bigint id PK
bigint user_id FK "nullable → users.id nullOnDelete"
string name
text description "nullable"
string metric
decimal target
decimal current "default 0"
date start_date
date end_date
string status "default active"
timestamp created_at
timestamp updated_at
}
SURVEYS {
bigint id PK
string title
text description "nullable"
string audience
char public_token "nullable"
boolean is_active "default true"
date starts_at "nullable"
date ends_at "nullable"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
SURVEY_QUESTIONS {
bigint id PK
bigint survey_id FK "→ surveys.id cascadeOnDelete"
string question
string type
json options "nullable"
boolean is_required "default false"
int order "default 0"
}
SURVEY_RESPONSES {
bigint id PK
bigint survey_id FK "→ surveys.id cascadeOnDelete"
bigint contact_id FK "nullable → contacts.id nullOnDelete"
char anonymous_token "nullable"
string ip_address "nullable"
timestamp submitted_at
}
SURVEY_ANSWERS {
bigint id PK
bigint response_id FK "→ survey_responses.id cascadeOnDelete"
bigint question_id FK "→ survey_questions.id cascadeOnDelete"
text answer "nullable"
}
ANNOUNCEMENTS {
bigint id PK
string title
longtext body
string audience
bigint author_id FK "nullable → users.id nullOnDelete"
timestamp publish_at
timestamp expires_at "nullable"
timestamp created_at
timestamp updated_at
}
NEWSLETTER_SUBSCRIBERS {
bigint id PK
string email UK
string name "nullable"
string source "nullable"
boolean is_active "default true"
timestamp confirmed_at "nullable"
timestamp unsubscribed_at "nullable"
timestamp created_at
timestamp updated_at
}
#8. Platform / Cross-Cutting ERD
erDiagram
USERS ||--o{ PROVIDERS : "created by"
PROVIDERS ||--o{ PROVIDER_CREDENTIALS : "stores"
USERS ||--o{ WEBHOOKS : "created by"
WEBHOOKS ||--o{ WEBHOOK_DELIVERIES : "dispatched"
CUSTOM_FIELDS ||--o{ CUSTOM_FIELD_VALUES : "stored as"
USERS ||--o{ AUDIT_LOG : "recorded"
USERS ||--o{ FILES : "uploaded by"
FILES ||--o{ FILES : "attachable polymorphic"
NOTIFICATIONS }o--|| USERS : "addressed to"
PROVIDERS {
bigint id PK
string name
string type "indexed"
string api_format
string base_url "nullable"
text api_key_encrypted "nullable"
json extra_headers "nullable"
json extra_config "nullable"
boolean is_active "default true, indexed"
int priority "default 0"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
PROVIDER_CREDENTIALS {
bigint id PK
bigint provider_id FK "→ providers.id cascadeOnDelete"
string key
text value_encrypted "nullable"
boolean is_secret "default true"
}
WEBHOOKS {
bigint id PK
string event "indexed"
string url
string secret
boolean is_active "default true"
bigint created_by FK "nullable → users.id nullOnDelete"
timestamp created_at
timestamp updated_at
}
WEBHOOK_DELIVERIES {
bigint id PK
bigint webhook_id FK "→ webhooks.id cascadeOnDelete"
json payload
int status_code "nullable"
text response_body "nullable"
int attempt "default 1"
timestamp delivered_at "nullable"
timestamp next_retry_at "nullable"
}
CUSTOM_FIELDS {
bigint id PK
string entity
string label
string field_key
string type
json options "nullable"
boolean is_required "default false"
boolean is_visible_to_customer "default false"
int order "default 0"
timestamp created_at
timestamp updated_at
}
CUSTOM_FIELD_VALUES {
bigint id PK
bigint custom_field_id FK "→ custom_fields.id cascadeOnDelete"
string subject_type
bigint subject_id
text value "nullable"
}
AUDIT_LOG {
bigint id PK
bigint user_id FK "nullable → users.id nullOnDelete"
string action "indexed"
string subject_type "nullable"
bigint subject_id "nullable"
json before "nullable"
json after "nullable"
string ip_address "nullable"
string user_agent "nullable"
timestamp created_at "indexed"
}
SETTINGS {
bigint id PK
string key UK
longtext value "nullable"
string type "default string"
string group "nullable"
boolean is_encrypted "default false"
timestamp updated_at "nullable"
}
NUMBER_SEQUENCES {
bigint id PK
string key
int year "nullable"
bigint current "default 0"
timestamp updated_at "nullable"
}
NOTIFICATIONS {
uuid id PK
string type
string notifiable_type
bigint notifiable_id
text data
timestamp read_at "nullable"
timestamp created_at
timestamp updated_at
}
FILES {
bigint id PK
string disk "default local"
string path
string original_name
string mime
bigint size_bytes
bigint uploaded_by FK "nullable → users.id nullOnDelete"
string attachable_type "nullable"
bigint attachable_id "nullable"
boolean is_public "default false"
timestamp created_at
timestamp updated_at
}
CURRENCIES {
bigint id PK
char code UK "3 chars"
string name
string symbol
decimal exchange_rate "default 1, 15,6"
boolean is_base "default false"
char decimal_separator "default ."
char thousand_separator "default ,"
timestamp created_at
timestamp updated_at
}
TAX_RATES {
bigint id PK
string name
decimal percentage "7,4"
boolean is_compound "default false"
boolean is_active "default true"
timestamp created_at
timestamp updated_at
}
#9. Complete FK Relationship Summary
| # |
Source Table |
Source Column |
Target Table |
Target Column |
ON DELETE |
Type |
| 1 |
users |
avatar_file_id |
files |
id |
SET NULL |
belongsTo |
| 2 |
sessions |
user_id |
users |
id |
— (index only) |
belongsTo |
| 3 |
files |
uploaded_by |
users |
id |
SET NULL |
belongsTo |
| 4 |
items |
default_tax_rate_id |
tax_rates |
id |
SET NULL |
belongsTo |
| 5 |
items |
currency_id |
currencies |
id |
SET NULL |
belongsTo |
| 6 |
clients |
account_manager_id |
users |
id |
SET NULL |
belongsTo |
| 7 |
clients |
default_currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 8 |
contacts |
client_id |
clients |
id |
CASCADE |
belongsTo |
| 9 |
leads |
currency_id |
currencies |
id |
SET NULL |
belongsTo |
| 10 |
leads |
lead_source_id |
lead_sources |
id |
SET NULL |
belongsTo |
| 11 |
leads |
lead_status_id |
lead_statuses |
id |
RESTRICT |
belongsTo |
| 12 |
leads |
assigned_to |
users |
id |
SET NULL |
belongsTo |
| 13 |
leads |
converted_to_client_id |
clients |
id |
SET NULL |
belongsTo |
| 14 |
activities |
user_id |
users |
id |
SET NULL |
belongsTo |
| 15 |
notes |
user_id |
users |
id |
SET NULL |
belongsTo |
| 16 |
providers |
created_by |
users |
id |
SET NULL |
belongsTo |
| 17 |
provider_credentials |
provider_id |
providers |
id |
CASCADE |
belongsTo |
| 18 |
projects |
client_id |
clients |
id |
RESTRICT |
belongsTo |
| 19 |
projects |
project_manager_id |
users |
id |
SET NULL |
belongsTo |
| 20 |
projects |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 21 |
project_members |
project_id |
projects |
id |
CASCADE |
belongsTo |
| 22 |
project_members |
user_id |
users |
id |
CASCADE |
belongsTo |
| 23 |
milestones |
project_id |
projects |
id |
CASCADE |
belongsTo |
| 24 |
tasks |
project_id |
projects |
id |
CASCADE |
belongsTo |
| 25 |
tasks |
milestone_id |
milestones |
id |
SET NULL |
belongsTo |
| 26 |
tasks |
parent_task_id |
tasks |
id |
SET NULL |
belongsTo (self) |
| 27 |
tasks |
created_by |
users |
id |
SET NULL |
belongsTo |
| 28 |
task_assignees |
task_id |
tasks |
id |
CASCADE |
belongsTo |
| 29 |
task_assignees |
user_id |
users |
id |
CASCADE |
belongsTo |
| 30 |
task_checklist |
task_id |
tasks |
id |
CASCADE |
belongsTo |
| 31 |
task_dependencies |
task_id |
tasks |
id |
CASCADE |
belongsTo |
| 32 |
task_dependencies |
depends_on_task_id |
tasks |
id |
CASCADE |
belongsTo |
| 33 |
time_entries |
task_id |
tasks |
id |
SET NULL |
belongsTo |
| 34 |
time_entries |
project_id |
projects |
id |
SET NULL |
belongsTo |
| 35 |
time_entries |
user_id |
users |
id |
CASCADE |
belongsTo |
| 36 |
time_entries |
invoice_item_id |
invoice_items |
id |
SET NULL |
belongsTo |
| 37 |
discussions |
project_id |
projects |
id |
CASCADE |
belongsTo |
| 38 |
discussions |
user_id |
users |
id |
SET NULL |
belongsTo |
| 39 |
discussion_replies |
discussion_id |
discussions |
id |
CASCADE |
belongsTo |
| 40 |
discussion_replies |
user_id |
users |
id |
SET NULL |
belongsTo |
| 41 |
discussion_replies |
contact_id |
contacts |
id |
SET NULL |
belongsTo |
| 42 |
estimates |
client_id |
clients |
id |
RESTRICT |
belongsTo |
| 43 |
estimates |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 44 |
estimates |
converted_invoice_id |
invoices |
id |
SET NULL |
belongsTo |
| 45 |
estimates |
created_by |
users |
id |
SET NULL |
belongsTo |
| 46 |
estimate_items |
estimate_id |
estimates |
id |
CASCADE |
belongsTo |
| 47 |
estimate_items |
item_id |
items |
id |
SET NULL |
belongsTo |
| 48 |
estimate_items |
tax_rate_id |
tax_rates |
id |
SET NULL |
belongsTo |
| 49 |
proposals |
client_id |
clients |
id |
SET NULL |
belongsTo |
| 50 |
proposals |
lead_id |
leads |
id |
SET NULL |
belongsTo |
| 51 |
proposals |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 52 |
proposals |
created_by |
users |
id |
SET NULL |
belongsTo |
| 53 |
contracts |
client_id |
clients |
id |
RESTRICT |
belongsTo |
| 54 |
contracts |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 55 |
contracts |
created_by |
users |
id |
SET NULL |
belongsTo |
| 56 |
invoices |
client_id |
clients |
id |
RESTRICT |
belongsTo |
| 57 |
invoices |
project_id |
projects |
id |
SET NULL |
belongsTo |
| 58 |
invoices |
estimate_id |
estimates |
id |
SET NULL |
belongsTo |
| 59 |
invoices |
recurring_parent_id |
invoices |
id |
SET NULL |
belongsTo (self) |
| 60 |
invoices |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 61 |
invoices |
pdf_file_id |
files |
id |
SET NULL |
belongsTo |
| 62 |
invoices |
created_by |
users |
id |
SET NULL |
belongsTo |
| 63 |
invoice_items |
invoice_id |
invoices |
id |
CASCADE |
belongsTo |
| 64 |
invoice_items |
item_id |
items |
id |
SET NULL |
belongsTo |
| 65 |
invoice_items |
time_entry_id |
time_entries |
id |
SET NULL |
belongsTo |
| 66 |
invoice_items |
expense_id |
expenses |
id |
SET NULL |
belongsTo |
| 67 |
invoice_items |
tax_rate_id |
tax_rates |
id |
SET NULL |
belongsTo |
| 68 |
payments |
invoice_id |
invoices |
id |
RESTRICT |
belongsTo |
| 69 |
payments |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 70 |
payments |
provider_id |
providers |
id |
SET NULL |
belongsTo |
| 71 |
credit_notes |
client_id |
clients |
id |
RESTRICT |
belongsTo |
| 72 |
credit_notes |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 73 |
credit_notes |
created_by |
users |
id |
SET NULL |
belongsTo |
| 74 |
credit_note_invoices |
credit_note_id |
credit_notes |
id |
CASCADE |
belongsTo |
| 75 |
credit_note_invoices |
invoice_id |
invoices |
id |
CASCADE |
belongsTo |
| 76 |
expenses |
expense_category_id |
expense_categories |
id |
SET NULL |
belongsTo |
| 77 |
expenses |
client_id |
clients |
id |
SET NULL |
belongsTo |
| 78 |
expenses |
project_id |
projects |
id |
SET NULL |
belongsTo |
| 79 |
expenses |
currency_id |
currencies |
id |
RESTRICT |
belongsTo |
| 80 |
expenses |
tax_rate_id |
tax_rates |
id |
SET NULL |
belongsTo |
| 81 |
expenses |
invoice_item_id |
invoice_items |
id |
SET NULL |
belongsTo |
| 82 |
expenses |
receipt_file_id |
files |
id |
SET NULL |
belongsTo |
| 83 |
expenses |
created_by |
users |
id |
SET NULL |
belongsTo |
| 84 |
departments |
default_assignee_id |
users |
id |
SET NULL |
belongsTo |
| 85 |
tickets |
client_id |
clients |
id |
SET NULL |
belongsTo |
| 86 |
tickets |
contact_id |
contacts |
id |
SET NULL |
belongsTo |
| 87 |
tickets |
department_id |
departments |
id |
RESTRICT |
belongsTo |
| 88 |
tickets |
priority_id |
ticket_priorities |
id |
RESTRICT |
belongsTo |
| 89 |
tickets |
status_id |
ticket_statuses |
id |
RESTRICT |
belongsTo |
| 90 |
tickets |
sla_policy_id |
sla_policies |
id |
SET NULL |
belongsTo |
| 91 |
tickets |
assigned_to |
users |
id |
SET NULL |
belongsTo |
| 92 |
tickets |
related_project_id |
projects |
id |
SET NULL |
belongsTo |
| 93 |
ticket_replies |
ticket_id |
tickets |
id |
CASCADE |
belongsTo |
| 94 |
ticket_replies |
user_id |
users |
id |
SET NULL |
belongsTo |
| 95 |
ticket_replies |
contact_id |
contacts |
id |
SET NULL |
belongsTo |
| 96 |
ticket_attachments |
ticket_id |
tickets |
id |
CASCADE |
belongsTo |
| 97 |
ticket_attachments |
ticket_reply_id |
ticket_replies |
id |
CASCADE |
belongsTo |
| 98 |
ticket_attachments |
file_id |
files |
id |
CASCADE |
belongsTo |
| 99 |
kb_categories |
parent_id |
kb_categories |
id |
SET NULL |
belongsTo (self) |
| 100 |
kb_articles |
category_id |
kb_categories |
id |
RESTRICT |
belongsTo |
| 101 |
kb_articles |
author_id |
users |
id |
SET NULL |
belongsTo |
| 102 |
kb_article_votes |
article_id |
kb_articles |
id |
CASCADE |
belongsTo |
| 103 |
calendar_events |
user_id |
users |
id |
CASCADE |
belongsTo |
| 104 |
calendar_event_invitees |
event_id |
calendar_events |
id |
CASCADE |
belongsTo |
| 105 |
calendar_event_invitees |
user_id |
users |
id |
CASCADE |
belongsTo |
| 106 |
goals |
user_id |
users |
id |
SET NULL |
belongsTo |
| 107 |
surveys |
created_by |
users |
id |
SET NULL |
belongsTo |
| 108 |
survey_questions |
survey_id |
surveys |
id |
CASCADE |
belongsTo |
| 109 |
survey_responses |
survey_id |
surveys |
id |
CASCADE |
belongsTo |
| 110 |
survey_responses |
contact_id |
contacts |
id |
SET NULL |
belongsTo |
| 111 |
survey_answers |
response_id |
survey_responses |
id |
CASCADE |
belongsTo |
| 112 |
survey_answers |
question_id |
survey_questions |
id |
CASCADE |
belongsTo |
| 113 |
announcements |
author_id |
users |
id |
SET NULL |
belongsTo |
| 114 |
custom_field_values |
custom_field_id |
custom_fields |
id |
CASCADE |
belongsTo |
| 115 |
audit_log |
user_id |
users |
id |
SET NULL |
belongsTo |
| 116 |
webhooks |
created_by |
users |
id |
SET NULL |
belongsTo |
| 117 |
webhook_deliveries |
webhook_id |
webhooks |
id |
CASCADE |
belongsTo |
| 118 |
model_has_roles |
role_id |
roles |
id |
CASCADE |
morphTo |
| 119 |
model_has_permissions |
permission_id |
permissions |
id |
CASCADE |
morphTo |
| 120 |
role_has_permissions |
permission_id |
permissions |
id |
CASCADE |
belongsTo |
| 121 |
role_has_permissions |
role_id |
roles |
id |
CASCADE |
belongsTo |
Total: 121 foreign keys across 50+ tables (excluding infrastructure: jobs, cache, password_reset_tokens, personal_access_tokens).
#10. Polymorphic Relationships
| Pivot |
*_type Column |
Values (examples) |
*_id Column |
activities.subject_* |
subject_type |
Client, Lead, Project, Invoice, Ticket, Task |
subject_id |
notes.notable_* |
notable_type |
Client, Lead, Project, Task, Ticket |
notable_id |
files.attachable_* |
attachable_type |
Any entity with file attachments |
attachable_id |
custom_field_values.subject_* |
subject_type |
Client, Lead, Project, Task, Invoice, Ticket |
subject_id |
calendar_events.related_* |
related_type |
Project, Task, Invoice, Ticket (nullable) |
related_id |
notifications.notifiable_* |
notifiable_type |
User, Contact |
notifiable_id |
personal_access_tokens.tokenable_* |
tokenable_type |
User |
tokenable_id |
audit_log.subject_* |
subject_type |
Any auditable entity |
subject_id |
model_has_roles.model_* |
model_type |
User |
model_id |
model_has_permissions.model_* |
model_type |
User |
model_id |
#11. ON DELETE Action Distribution
| ON DELETE |
Count |
Usage |
| CASCADE |
38 |
Pivot/junction tables, child entities (items, replies, checklist, dependencies) |
| SET NULL (nullable FK) |
66 |
Optional references like assigned_to, created_by, optional links |
| RESTRICT |
17 |
Core business FKs: client_id on invoices/estimates/projects, currency_id, status_id, priority_id |
#12. Indexes (Key Performance)
| Table |
Index |
Purpose |
clients |
(status), (company_name) |
Active clients, search |
leads |
(last_activity_at), (lead_status_id), (assigned_to) |
Kanban board, assignment |
activities |
(subject_type, subject_id), (occurred_at) |
Activity feeds, timeline |
notes |
(notable_type, notable_id) |
Polymorphic notes lookup |
tasks |
(status), (due_date) |
Task board, deadline alerts |
time_entries |
(is_billable, is_invoiced) |
Unbilled hours report |
estimates |
(status) |
Status-based filtering |
invoices |
(status), (due_date), (next_recurring_date) |
Overdue, recurring generation |
payments |
(transaction_id) |
Deduplication |
tickets |
(first_response_due_at) |
SLA breach detection |
kb_articles |
(is_published, published_at) |
Public KB listing |
calendar_events |
(starts_at), (related_type, related_id) |
Calendar fetch, entity links |
files |
(attachable_type, attachable_id) |
Polymorphic file attachments |
custom_field_values |
(subject_type, subject_id), uq_cfv |
Polymorphic CF lookup |
audit_log |
(action), (created_at), (subject_type, subject_id) |
Audit trails, filtering |
providers |
(type), (is_active) |
Provider selection |
webhooks |
(event) |
Event dispatch matching |
users |
(is_active) |
Active user queries |
#13. Table Count Summary
| Domain |
Tables |
Description |
| Identity |
9 |
users, roles, permissions, 3× pivot, PAT, sessions, pwd_reset |
| CRM |
5 |
clients, contacts, leads, activities, notes |
| Sales |
12 |
estimates, estimate_items, proposals, contracts, invoices, invoice_items, payments, credit_notes, credit_note_invoices, items, expenses, expense_categories |
| Projects |
8 |
projects, project_members, milestones, tasks, task_assignees, task_checklist, task_dependencies, time_entries |
| Discussions |
2 |
discussions, discussion_replies |
| Support |
8 |
departments, tickets, ticket_replies, ticket_attachments, ticket_priorities, ticket_statuses, sla_policies |
| Knowledge Base |
3 |
kb_categories, kb_articles, kb_article_votes |
| Engagement |
7 |
calendar_events, calendar_event_invitees, goals, surveys, survey_questions, survey_responses, survey_answers |
| Announcements |
2 |
announcements, newsletter_subscribers |
| Platform |
11 |
providers, provider_credentials, webhooks, webhook_deliveries, custom_fields, custom_field_values, audit_log, settings, number_sequences, notifications, files |
| Reference |
2 |
currencies, tax_rates |
| Infrastructure |
5 |
jobs, job_batches, failed_jobs, cache, cache_locks |
| TOTAL |
74 |
Including all Spatie RBAC, Laravel queues, cache |