Database tables

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- COA (register) accounts

CREATE TABLE accounts(
    account_id UUID NOT NULL,
    account_name TEXT NOT NULL, -- account name
    account_number NUMERIC(7,3) NOT NULL, -- this is unique within a single company
    account_type NUMERIC(2,0) NOT NULL, -- see accountd.json for defined types
    active BOOLEAN DEFAULT true,
    company_id UUID NOT NULL, -- the company that owns this account
    starting_value NUMERIC(12,2) DEFAULT 0, -- any value accounts for valuation before data starts
    summary_account BOOLEAN DEFAULT false, -- this is a summary account (non-posting)
    CONSTRAINT accounts_pkey PRIMARY KEY (company_id, account_number),
    CONSTRAINT accounts_account_id_key UNIQUE (account_id)
);


-- Adhoc comments

CREATE TABLE adhoc_comments (
    comment_body TEXT NOT NULL, -- the comments text
    comment_id UUID NOT NULL, -- the comment ID
    comment_title TEXT, -- the comment title
    created BIGINT NOT NULL, -- Unix timestamp
    last_updated BIGINT NOT NULL, -- Unix timestamp
    owner UUID NOT NULL, -- comment owner
    topic_id UUID NOT NULL, -- topic ID
    CONSTRAINT adhoc_comments_pkey PRIMARY KEY (comment_id)
);


-- Adhoc followups

CREATE TABLE adhoc_followups (
  assigned        UUID          NOT NULL,  -- who is assigned to take care of this
  completed       BOOLEAN       DEFAULT FALSE, -- is this done?
  followup        TEXT          NOT NULL, -- the followup title/primary action
  followup_id     UUID          NOT NULL, -- unique ID
  followup_time   NUMERIC(10,0) NOT NULL, -- Unix timestamp
  last_updated    NUMERIC(10,0) NOT NULL, -- Unix timestamp
  last_updated_by UUID          NOT NULL, -- who updated this last?
  topic_id        UUID          NOT NULL, -- related Adhoc topic ID
  CONSTRAINT adhoc_followups_pkey1 PRIMARY KEY (followup_id)
);

-- Adhoc followup comments

CREATE TABLE adhoc_followup_comments (
  comment_id    UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- unique comment ID
  comments      TEXT NOT NULL, -- comment text
  followup_id   UUID NOT NULL, -- followup_id
  last_updated  BIGINT NOT NULL, -- Unix timestamp
  position      SMALLINT NOT NULL, -- comment position to ensure display order
  user_id       UUID NOT NULL, -- who made the comment
  CONSTRAINT unique_followup_position UNIQUE (followup_id, position),
  CONSTRAINT adhoc_followup_comments_followup_id_fkey
    FOREIGN KEY (followup_id)
    REFERENCES adhoc_followups(followup_id)
    ON DELETE CASCADE
);



-- Adhoc topics

CREATE TABLE adhoc_topics (
    active        BOOLEAN       DEFAULT true, -- is the topic active
    created       BIGINT NOT NULL, -- Unix timestamp
    flagged       SMALLINT  DEFAULT 0, -- is the topic flagged
    last_updated  BIGINT NOT NULL, -- Unix timestamp
    locked        SMALLINT, -- users at a lower level cannot change records here
    owner         UUID          NOT NULL, -- user ID
    topic_id      UUID          NOT NULL, -- topic ID
    topic_title   TEXT          NOT NULL, -- topic title
    updated_by    UUID          NOT NULL, -- who updated this last?
    CONSTRAINT adhoc_topics_topic_id_key UNIQUE (topic_id)
);

-- Alarms

CREATE TABLE alarms (
    alarm_id UUID NOT NULL, -- unique ID
    alarm_text TEXT NOT NULL, -- alarm notes
    date DATE NOT NULL, -- creation date
    item_id UUID NOT NULL, -- the connected item
    user_id UUID NOT NULL, -- who created this alarm
    CONSTRAINT alarms_pkey PRIMARY KEY (alarm_id)
);

-- Check payees

CREATE TABLE check_payees (
    active BOOLEAN DEFAULT true,
    company_id UUID NOT NULL, -- who owns (can use) this payee
    payee_name CHARACTER VARYING(100) NOT NULL,
    CONSTRAINT check_payees_pkey PRIMARY KEY (company_id, payee_name)
);


-- Check templates

CREATE TABLE check_templates (
    active BOOLEAN DEFAULT true,
    amount NUMERIC(11,2),
    check_number CHARACTER VARYING(30),
    check_type SMALLINT NOT NULL,
    company_id UUID,
    memo CHARACTER VARYING(60),
    payee_name CHARACTER VARYING(100) NOT NULL,
    payment_account UUID,
    source_account UUID,
    tags TEXT,
    template_id UUID,
    template_name CHARACTER VARYING(60) NOT NULL,
    template_notes TEXT,
    CONSTRAINT check_templates_pkey PRIMARY KEY (template_id)
);


-- Checks

CREATE TABLE checks (
    check_id UUID NOT NULL,
    check_number CHARACTER VARYING(30) NOT NULL,
    company_id UUID NOT NULL,
    date DATE NOT NULL,
    locked SMALLINT DEFAULT 0,
    memo CHARACTER VARYING(60),
    payee CHARACTER VARYING(100) NOT NULL,
    type SMALLINT NOT NULL,
    CONSTRAINT checks_pkey PRIMARY KEY (check_id)
);

-- Sales commission records

CREATE TABLE commissions (
    commission_id UUID NOT NULL,
    cost_basis NUMERIC(11,2),
    expense_id UUID NOT NULL,
    item_id UUID NOT NULL,
    lock SMALLINT DEFAULT 0,
    notes TEXT,
    percentage NUMERIC(4,2) NOT NULL,
    sku CHARACTER VARYING(60),
    user_id UUID NOT NULL,
    CONSTRAINT commissions_pkey PRIMARY KEY (commission_id),
    CONSTRAINT unique_user_item_combination UNIQUE (user_id, item_id)
);


-- Company settings are defined on a per-company basis

CREATE TABLE companies(
    accountd BOOLEAN DEFAULT false, -- enables accounting tools
    active BOOLEAN DEFAULT true,
    address TEXT,
    commission_expenses UUID, -- default posting account for commission expenses
    company_id UUID NOT NULL,
    company_name TEXT NOT NULL,
    contact_info TEXT,
    gone_location UUID, -- used in inventory to define when items are gone
    healthica UUID, -- the API key used to access healthica.com
    inventory_costs UUID, -- default posting account for inventory costs
    logo_path TEXT, -- URL for the company logo
    notes TEXT, -- private notes (optional)
    overhead UUID, -- default posting account for overhead expenses
    quote_expiration NUMERIC(3,0), -- # days a quote is allowed to "live"
    refunds UUID, -- default posting account for sales refunds
    restricted BOOLEAN, -- company_access record is required
    returns_due NUMERIC(3,0) DEFAULT 30, -- # days to return a core
    service_costs UUID, -- default posting account for service costs
    servicd BOOLEAN DEFAULT false, -- enables service tools
    stockd BOOLEAN DEFAULT false, -- enables sales/inventory tools
    tagline TEXT, -- optional slogan line
    undeposited UUID -- default undeposited funds account,
    labor_1 NUMERIC(6,2) NOT NULL DEFAULT 0,
    labor_2 NUMERIC(6,2) NOT NULL DEFAULT 0,
    labor_3 NUMERIC(6,2) NOT NULL DEFAULT 0,
    travel_1 NUMERIC(6,2) NOT NULL DEFAULT 0,
    travel_2 NUMERIC(6,2) NOT NULL DEFAULT 0,
    travel_3 NUMERIC(6,2) NOT NULL DEFAULT 0,
    CONSTRAINT companies_pkey PRIMARY KEY (company_id)
);


-- Company access determines which users can see which companies

CREATE TABLE company_access(
    company_id UUID NOT NULL,
    user_id UUID NOT NULL,
    CONSTRAINT unique_company_access_record UNIQUE (company_id, user_id)
);

-- Contacts

CREATE TABLE contacts (
    accountd BOOLEAN DEFAULT true,
    active BOOLEAN DEFAULT true,
    company_name CHARACTER VARYING(100),
    contact_id UUID NOT NULL,
    email CHARACTER VARYING(100),
    full_name CHARACTER VARYING(100),
    linked_in VARCHAR(100),
    notes TEXT,
    phone CHARACTER VARYING(30),
    servicd BOOLEAN DEFAULT true,
    sms CHARACTER VARYING(30),
    stockd BOOLEAN DEFAULT true,
    terms_default UUID,
    CONSTRAINT contacts_pkey PRIMARY KEY (contact_id),
    CONSTRAINT name_email UNIQUE (email, full_name)
); 


-- Cost source expense type defaults

CREATE TABLE cost_source_expense_types (
    company_id UUID NOT NULL,
    expense_type UUID NOT NULL,
    CONSTRAINT unique_company_source_expense_type UNIQUE (company_id, expense_type)
);


-- Cost source tags

CREATE TABLE cost_source_tags (
    company_id UUID NOT NULL,
    tag_id UUID NOT NULL,
    CONSTRAINT unique_company_source_tag UNIQUE (company_id, tag_id)
);


-- Cost sources

CREATE TABLE cost_sources (
    allow_analysis BOOLEAN DEFAULT true, -- this source will appear in analysis
    allow_costs BOOLEAN DEFAULT true, -- the cost inputs can be changed
    allow_items BOOLEAN DEFAULT true, -- the connected items can be changed
    company_id UUID NOT NULL, -- the company that owns the source
    description TEXT, -- optional description 
    locked SMALLINT DEFAULT 0,
    source_id UUID NOT NULL,
    source_name TEXT NOT NULL,
    source_type SMALLINT, -- 1 = source expense, 2 = source job, 3 = source group
    updated BIGINT,
    CONSTRAINT cost_sources_pkey PRIMARY KEY (source_id)
);


-- credit cards

CREATE TABLE credit_cards (
    active           BOOLEAN DEFAULT true,
    card_id          UUID    NOT NULL,
    card_name        TEXT    NOT NULL,
    company_id       UUID    NOT NULL,
    notes            TEXT,
    posting_account  UUID    NOT NULL,
    CONSTRAINT credit_cards_pkey PRIMARY KEY (card_id)
);


-- credit memos

CREATE TABLE credit_memos (
    amount NUMERIC(11,2) NOT NULL,
    date DATE NOT NULL,
    locked SMALLINT DEFAULT 0,
    memo_id UUID NOT NULL,
    posting_account UUID NOT NULL,
    reason TEXT NOT NULL,
    CONSTRAINT credit_memos_pkey PRIMARY KEY (memo_id)
);


-- CRM stage types and templates

CREATE TABLE crm_stage_types (
    locked SMALLINT DEFAULT(0),                                                 -- governs whether a user can modify the record
    open BOOLEAN NOT NULL DEFAULT true,                                         -- Can stages be moved into this type (group)      
    type_id UUID PRIMARY KEY,                                                   -- Unique identifier for the stage type
    type_name VARCHAR(100) UNIQUE NOT NULL,                                     -- Human-readable type name    
    type_notes TEXT,                                                            -- Optional notes or checklist for the stage type
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- User ID who last updated this stage type
);

CREATE TABLE crm_stage_templates (
    active BOOLEAN NOT NULL DEFAULT true,                                       -- If the template is currently active
    auto_advance BOOLEAN NOT NULL DEFAULT true,                                 -- Whether this stage auto-advances when criteria are met
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    stage_label VARCHAR(100) NOT NULL,                                          -- display label for the stage
    stage_name VARCHAR(100) NOT NULL,                                           -- internal stage identifier (e.g., 'proposal_sent')
    stage_notes TEXT,                                                           -- optional notes for the stage
    stage_type UUID NOT NULL,                                                   -- foreign key to a stage type or category
    template_id UUID PRIMARY KEY,                                               -- unique identifier for the template
    template_name VARCHAR(100) UNIQUE NOT NULL,                                 -- human-readable template name
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this template

    CONSTRAINT fk_stage_type
      FOREIGN KEY (stage_type)
      REFERENCES crm_stage_types (type_id)
      ON UPDATE CASCADE
      ON DELETE RESTRICT
);

-- For tracking stage templates changes
CREATE INDEX idx_stage_templates_updated_by ON crm_stage_templates(updated_by);

-- For filtering stage templates by active
CREATE INDEX idx_stage_templates_active ON crm_stage_templates(active);

-- For filtering stage templates by stage type
CREATE INDEX idx_stage_templates_stage_type ON crm_stage_templates(stage_type);

CREATE TABLE crm_prebuilt_stages (
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the stage is currently active
    auto_advance BOOLEAN NOT NULL DEFAULT true,                                 -- whether this stage auto-advances when criteria are met
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    stage_id UUID PRIMARY KEY,                                                  -- the stage ID
    stage_label VARCHAR(100) NOT NULL,                                          -- display label for the stage
    stage_name VARCHAR(100) NOT NULL,                                           -- internal stage identifier (e.g., 'proposal_sent')
    stage_notes TEXT,                                                           -- optional description
    stage_type UUID NOT NULL,                                                   -- foreign key to a stage type or category
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this template

    CONSTRAINT fk_stage_type
      FOREIGN KEY (stage_type)
      REFERENCES crm_stage_types (type_id)
      ON UPDATE CASCADE
      ON DELETE RESTRICT
);

-- For filtering/joining by stage type
CREATE INDEX idx_prebuilt_stage_type ON crm_prebuilt_stages(stage_type);

-- For filtering on active status
CREATE INDEX idx_prebuilt_stage_active ON crm_prebuilt_stages(active);

-- For filtering on who updated the prebuilt stage last
CREATE INDEX idx_prebuilt_stage_updated_by ON crm_prebuilt_stages(updated_by);


-- CRM task templates are starting templates which are cloned into prebuilt stages.

CREATE TABLE crm_task_templates (
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the stage task is currently active
    description TEXT,                                                           -- optional additional information
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    optional BOOLEAN DEFAULT false,                                             -- is this task mandatory to advance the stage?
    task_id UUID PRIMARY KEY,                                                   -- the stage task ID
    task_name VARCHAR(100) NOT NULL,                                            -- the stage task name.
    task_notes TEXT,                                                            -- optional notes
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- user ID who last updated this template
);

-- For filtering on who updated the task template last
CREATE INDEX idx_task_templates_updated_by ON crm_task_templates(updated_by);

-- For filtering on active status
CREATE INDEX idx_task_templates_active ON crm_task_templates(active);


-- Prebuilt stage tasks are clonable task records used when creating workflows.

CREATE TABLE crm_prebuilt_stage_tasks (
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the stage task is currently active
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    optional BOOLEAN DEFAULT false,                                             -- is this task mandatory to advance the stage?
    stage_id UUID NOT NULL,                                                     -- task is p/o this prebuilt stage
    task_id UUID PRIMARY KEY,                                                   -- the stage task ID
    task_name VARCHAR(100) NOT NULL,                                            -- the stage task name
    task_notes TEXT,                                                            -- optional notes
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this template

    CONSTRAINT fk_prebuilt_stage_id
      FOREIGN KEY (stage_id)
      REFERENCES crm_prebuilt_stages (stage_id)
      ON UPDATE CASCADE
      ON DELETE RESTRICT
);

-- For filtering/grouping rebuilt tasks by stage
CREATE INDEX idx_prebuilt_stage_tasks_stage_id ON crm_prebuilt_stage_tasks(stage_id);

-- b-tree efficient indexing (fuzzy search support)
CREATE INDEX idx_stage_templates_stage_label_trgm ON crm_stage_templates USING gin (stage_label gin_trgm_ops);
CREATE INDEX idx_stage_templates_stage_name_trgm ON crm_stage_templates USING gin (stage_name gin_trgm_ops);
CREATE INDEX idx_stage_templates_template_name_trgm ON crm_stage_templates USING gin (template_name gin_trgm_ops);


-- CRM teams, roles and team members

CREATE TABLE crm_team_roles(
    description TEXT,                                                           -- optional description
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    role_id UUID PRIMARY KEY,                                                   -- unique ID for the team role
    role_name VARCHAR(100) UNIQUE NOT NULL,                                     -- the role name
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- user ID who last updated this team role
);

-- Filtering by who last updated
CREATE INDEX idx_team_roles_updated_by ON crm_team_roles(updated_by);

CREATE TABLE crm_teams(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the team is currently active
    description TEXT,                                                           -- optional description
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    team_id UUID PRIMARY KEY,                                                   -- unique identifier for the team
    team_name VARCHAR(100) UNIQUE NOT NULL,                                     -- unique workflow name
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- user ID who last updated this team
);

-- Filtering by who last updated
CREATE INDEX idx_teams_updated_by ON crm_teams(updated_by);

-- Filtering by active teams
CREATE INDEX idx_teams_active ON crm_teams(active);

CREATE TABLE crm_team_members(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the team member is currently active
    description TEXT,                                                           -- optional description
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    member_id UUID PRIMARY KEY,                                                 -- record identifier
    role UUID NOT NULL REFERENCES crm_team_roles(role_id),                      -- links to crm_team_roles
    team_id UUID NOT NULL REFERENCES crm_teams(team_id),                        -- the sale team
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this workflow
    user_id UUID NOT NULL,                                                      -- the actual user ID
    UNIQUE (team_id, user_id)
);

-- For fast lookups by team
CREATE INDEX idx_team_members_team_id ON crm_team_members(team_id);

-- For fast lookups by user
CREATE INDEX idx_team_members_user_id ON crm_team_members(user_id);

-- For filtering by updater
CREATE INDEX idx_team_members_updated_by ON crm_team_members(updated_by);

-- For filtering by role
CREATE INDEX idx_team_members_role ON crm_team_members(role);


-- CRM workflows (from this point on, CRM templates can cascade changes)

CREATE TABLE crm_workflows(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the workflow is currently active
    description TEXT,                                                           -- optional description
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this workflow
    workflow_id UUID PRIMARY KEY,                                                -- unique indentifier for the workflow
    workflow_name VARCHAR(100) UNIQUE NOT NULL                                  -- unique workflow name
);

-- Fast retrieval by who updated
CREATE INDEX idx_workflows_updated_by ON crm_workflows(updated_by);

-- Filtering for active workflows
CREATE INDEX idx_workflows_active ON crm_workflows(active);

-- Which teams can use the workflow in a project?

CREATE TABLE crm_workflow_access(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the workflow access is currently active
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    team_id UUID NOT NULL REFERENCES crm_teams(team_id),                        -- teams authorized to use this workflow
    workflow_id UUID NOT NULL REFERENCES crm_workflows(workflow_id)
      ON DELETE CASCADE,                                                        -- the workflow ID
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this access record
    UNIQUE (team_id, workflow_id)
);

-- Allow lookups by workflow
CREATE INDEX idx_workflow_access_by_workflow ON crm_workflow_access(workflow_id);

-- Allow lookups by updater
CREATE INDEX idx_workflow_access_updated_by ON crm_workflow_access(updated_by);

-- Filtering by active status
CREATE INDEX idx_workflow_access_active ON crm_workflow_access(active);


-- Stages (in position order) within the workflow.

CREATE TABLE crm_workflow_stages(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the workflow stage is currently available to funnels
    auto_advance BOOLEAN NOT NULL DEFAULT true,                                 -- whether this stage auto-advances when criteria are met
    locked SMALLINT,                                                            -- governs whether a user can modify the record
    prebuilt_id UUID NOT NULL REFERENCES crm_prebuilt_stages(stage_id),         -- the prebuilt stage that was cloned here
    stage_id UUID PRIMARY KEY,                                                  -- the stage ID
    stage_label VARCHAR(100) NOT NULL,                                          -- display label for the stage
    stage_name VARCHAR(100) NOT NULL,                                           -- internal stage identifier (e.g., 'proposal_sent')
    stage_notes TEXT,                                                           -- optional description
    stage_number SMALLINT NOT NULL,                                             -- orders the stages in a workflow
    stage_status SMALLINT NOT NULL DEFAULT 0,                                   -- stage status references static.json.crmStageStatus
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this workflow
    workflow_id UUID NOT NULL REFERENCES crm_workflows(workflow_id)
      ON DELETE CASCADE                                                         -- the workflow ID
);

-- Lookup or join stages by workflow
CREATE INDEX idx_workflow_stages_workflow_id ON crm_workflow_stages(workflow_id);

-- Lookup or audit trail
CREATE INDEX idx_workflow_stages_updated_by ON crm_workflow_stages(updated_by);

-- Filtering by active flag
CREATE INDEX idx_workflow_stages_active ON crm_workflow_stages(active);

-- Filtering/sorting by stage number within workflow
CREATE INDEX idx_workflow_stages_number ON crm_workflow_stages(workflow_id, stage_number);


-- Tasks within a CRM workflow are based on templates from crm_prebuilt_stage_tasks.

CREATE TABLE crm_workflow_stage_tasks(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the workflow task is currently active
    locked SMALLINT NOT NULL DEFAULT 0,                                         -- governs whether a user can modify the record
    optional BOOLEAN DEFAULT false,                                             -- is this task mandatory to advance the stage?
    prebuilt_id UUID NOT NULL REFERENCES crm_prebuilt_stage_tasks(task_id),     -- the prebuilt stage that was cloned here
    stage_id UUID NOT NULL REFERENCES crm_workflow_stages(stage_id)
      ON DELETE CASCADE,                                                        -- link to the stage metadata
    status SMALLINT NOT NULL DEFAULT 0,                                         -- task status references static.json.crmTaskStatus
    task_id UUID PRIMARY KEY,                                                   -- the unique ID of this assigned task
    task_name VARCHAR(100) NOT NULL,                                            -- the task name
    task_notes TEXT,                                                            -- optional notes
    task_number SMALLINT NOT NULL,                                              -- orders the tasks in a workflow stage
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this workflow
    workflow_id UUID NOT NULL REFERENCES crm_workflows(workflow_id)
      ON DELETE CASCADE                                                         -- the workflow ID
);

-- Lookup tasks by workflow
CREATE INDEX idx_workflow_stage_tasks_workflow_id ON crm_workflow_stage_tasks(workflow_id);

-- Lookup tasks within a stage
CREATE INDEX idx_workflow_stage_tasks_stage_id ON crm_workflow_stage_tasks(stage_id);

-- Lookup tasks by assignee
CREATE INDEX idx_workflow_stage_tasks_assigned ON crm_workflow_stage_tasks(assigned);

-- Lookup tasks by due date
CREATE INDEX idx_workflow_stage_tasks_due ON crm_workflow_stage_tasks(task_due);

-- Lookup/update audit
CREATE INDEX idx_workflow_stage_tasks_updated_by ON crm_workflow_stage_tasks(updated_by);

-- Filtering on active tasks
CREATE INDEX idx_workflow_stage_tasks_active ON crm_workflow_stage_tasks(active);


-- CRM workflow tasks may be disabled until prequisite tasks are completed or marked as ignored (if optional).

CREATE TABLE crm_workflow_task_prerequisites(
    prereq UUID NOT NULL REFERENCES crm_workflow_stage_tasks(task_id),          -- the unique ID of the prequisite task
    task_id UUID NOT NULL REFERENCES crm_workflow_stage_tasks(task_id),         -- the unique ID of the master task
    CONSTRAINT chk_task_cannot_depend_on_itself CHECK (prereq <> task_id),      -- the task cannot depend on itself
    UNIQUE (prereq, task_id)
);

-- For fast lookup of prereqs for a given task
CREATE INDEX idx_prereqs_by_task ON crm_workflow_task_prerequisites(task_id);

-- For reverse lookup (which tasks are gated by a prereq)
CREATE INDEX idx_prereqs_by_prereq ON crm_workflow_task_prerequisites(prereq);

CREATE TABLE crm_campaign_groups(
    active BOOLEAN DEFAULT true,                                                -- can new campaigns use this group?
    group_id UUID PRIMARY KEY,                                                  -- the unique group ID
    group_name VARCHAR(100) UNIQUE NOT NULL,                                    -- the unique group name
    locked SMALLINT DEFAULT(0),                                                 -- governs whether a user can modify the record
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- user ID who last updated this campaign
);


-- CRM campaigns are created 1:1 for a single contact or customer ID.

CREATE TABLE crm_campaigns(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the campaign is currently active
    campaign_group UUID NOT NULL REFERENCES crm_campaign_groups(group_id),      -- all campaigns belong to a group
    campaign_id UUID PRIMARY KEY,                                               -- unique indentifier for the campaign
    campaign_name VARCHAR(100) UNIQUE NOT NULL,                                 -- unique campaign name
    description TEXT,                                                           -- optional description
    locked SMALLINT DEFAULT(0),                                                 -- governs whether a user can modify the record
    target_id UUID NOT NULL REFERENCES contacts(contact_id),                    -- target must exist in contacts
    team_id UUID NOT NULL REFERENCES crm_teams(team_id),                        -- the team running this campaign,
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL,                                                   -- user ID who last updated this campaign
    workflow_id UUID NOT NULL REFERENCES crm_workflows(workflow_id)             -- workflow template used as source
);

-- Filter or sort by who last updated.
CREATE INDEX idx_campaigns_updated_by ON crm_campaigns(updated_by);

-- Active campaigns will be filtered often.
CREATE INDEX idx_campaigns_active ON crm_campaigns(active);

-- Full text searching/filtering support.
CREATE INDEX idx_campaigns_name_trgm ON crm_campaigns USING gin (campaign_name gin_trgm_ops);

-- Lookup by contact id.
CREATE INDEX idx_campaigns_target_id ON crm_campaigns(target_id);

-- Composite index for common filter queries.
CREATE INDEX idx_crm_campaigns_active_target_id ON crm_campaigns(active, target_id);


-- Stages (in position order) within the campaign.

CREATE TABLE crm_campaign_stages(
    active BOOLEAN NOT NULL DEFAULT false,                                      -- if the campaign stage is currently active (only one at a time)
    auto_advance BOOLEAN NOT NULL DEFAULT true,                                 -- whether this stage auto-advances when criteria are met
    locked SMALLINT DEFAULT(0),                                                 -- governs whether a user can modify the record
    campaign_id UUID NOT NULL REFERENCES crm_campaigns(campaign_id)             -- link to the campaign metadata
      ON DELETE CASCADE,
    prebuilt_id UUID NOT NULL REFERENCES crm_prebuilt_stages(stage_id),         -- the prebuilt stage that was cloned here (used to get tasks)
    stage_id UUID PRIMARY KEY,                                                  -- the unique ID of the stage
    stage_label VARCHAR(100) NOT NULL,                                          -- display label for the stage
    stage_manager UUID NOT NULL REFERENCES crm_team_members(member_id),         -- the team member responsible for this stage
    stage_name VARCHAR(100) NOT NULL,                                           -- internal stage identifier (e.g., 'proposal_sent')
    stage_notes TEXT,                                                           -- optional description
    stage_number SMALLINT NOT NULL,                                             -- orders the stages in a campaign
    stage_status SMALLINT NOT NULL DEFAULT(0),                                  -- stage status references static.json.crmStageStatus
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- user ID who last updated this campaign
);

-- Find stages by campaign
CREATE INDEX idx_campaign_stages_campaign_id ON crm_campaign_stages(campaign_id);

-- Who last updated
CREATE INDEX idx_campaign_stages_updated_by ON crm_campaign_stages(updated_by);

-- Filter stages by manager
CREATE INDEX idx_campaign_stages_stage_manager ON crm_campaign_stages(stage_manager);

-- Support for stage filtering (e.g. "active", "in progress", etc.)
CREATE INDEX idx_campaign_stages_status ON crm_campaign_stages(stage_status);

-- Filter by active stages only
CREATE INDEX idx_campaign_stages_active ON crm_campaign_stages(active);


-- Tasks within a CRM campaign.

CREATE TABLE crm_campaign_tasks(
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the campaign task is currently active
    assigned UUID NOT NULL REFERENCES crm_team_members(member_id),              -- the team member assigned to this task
    assigned_time BIGINT NOT NULL,                                              -- epoch format date when this was assigned
    locked SMALLINT DEFAULT(0),                                                 -- governs whether a user can modify the record
    optional BOOLEAN DEFAULT false,                                             -- is this task mandatory to advance the stage?
    prebuilt_id UUID NOT NULL REFERENCES crm_prebuilt_stage_tasks(task_id),     -- the prebuilt task that was the source for this record
    stage_id UUID NOT NULL REFERENCES crm_campaign_stages(stage_id)             -- link to the stage metadata
      ON DELETE CASCADE,
    task_due BIGINT,                                                            -- epoch format date when this is due
    task_id UUID PRIMARY KEY,                                                   -- the unique ID of this assigned task
    task_number SMALLINT NOT NULL,                                              -- orders the tasks in a workflow stage
    task_name VARCHAR(100) NOT NULL,                                            -- the task name
    task_notes TEXT,                                                            -- optional notes
    task_status SMALLINT NOT NULL DEFAULT 1,                                    -- task status references static.json.crmTaskStatus
    updated BIGINT NOT NULL,                                                    -- epoch timestamp of last update
    updated_by UUID NOT NULL                                                    -- user ID who last updated this workflow
);

-- Filter by assigned team member
CREATE INDEX idx_campaign_tasks_assigned ON crm_campaign_tasks(assigned);

-- Filter by parent stage
CREATE INDEX idx_campaign_tasks_stage_id ON crm_campaign_tasks(stage_id);

-- Filter by due date (e.g., for reminders/dashboards)
CREATE INDEX idx_campaign_tasks_due ON crm_campaign_tasks(task_due);

-- Filter by task status
CREATE INDEX idx_campaign_tasks_status ON crm_campaign_tasks(task_status);

-- Who updated the task
CREATE INDEX idx_campaign_tasks_updated_by ON crm_campaign_tasks(updated_by);

-- Filter only active tasks
CREATE INDEX idx_campaign_tasks_active ON crm_campaign_tasks(active);

-- Composite index for filter queries.
CREATE INDEX idx_crm_campaign_tasks_active_task_due ON crm_campaign_tasks(active, task_due);

-- Campaign task prerequisites.

CREATE TABLE crm_campaign_task_prerequisites (
    prereq UUID NOT NULL REFERENCES crm_campaign_tasks(task_id),                -- prerequisite task
    task_id UUID NOT NULL REFERENCES crm_campaign_tasks(task_id),               -- dependent task
    UNIQUE (prereq, task_id),
    CHECK (prereq <> task_id)                                                   -- disallow a task depending on itself
);

-- Filter on task ID and prerequisites
CREATE INDEX idx_campaign_task_prereqs_by_task ON crm_campaign_task_prerequisites(task_id);
CREATE INDEX idx_campaign_task_prereqs_by_prereq ON crm_campaign_task_prerequisites(prereq);


-- CRM campaign actions (registering custom actions)

CREATE TABLE crm_campaign_actions(
    action_id UUID PRIMARY KEY,                                                 -- record identifier
    action_name TEXT NOT NULL UNIQUE,                                           -- the action name
    active BOOLEAN NOT NULL DEFAULT true,                                       -- if the action is available for use
    description TEXT,                                                           -- optional description
    locked NUMERIC(1,0)                                                         -- governs whether a user can modify the record     
);


-- CRM campaign events

CREATE TABLE crm_campaign_events(
    action_id UUID NOT NULL REFERENCES crm_campaign_actions(action_id),         -- the action taken
    campaign_id UUID NOT NULL REFERENCES crm_campaigns(campaign_id)              -- the campaign
        ON DELETE CASCASE,
    event UUID PRIMARY KEY,                                                     -- record identifier,
    event_notes TEXT,                                                           -- optional event notes
    flagged BOOLEAN NOT NULL DEFAULT false,                                     -- flagged for review
    locked SMALLINT NOT NULL DEFAULT 0,                                         -- governs whether a user can modify the record
    rating SMALLINT NOT NULL,                                                   -- -1/0/1
    task_id UUID NOT NULL references crm_campaign_tasks(task_id),               -- task ID
    updated BIGINT NOT NULL,                                                    -- Epoch timestamp
    updated_by UUID NOT NULL                                                    -- user ID
);

-- Makes joins and deletes efficient by indexing foreign keys.
CREATE INDEX idx_crm_campaign_actions_action_id ON crm_campaign_events(action_id);
CREATE INDEX idx_crm_campaign_events_campaign_id ON crm_campaign_events(campaign_id);
CREATE INDEX idx_crm_campaign_events_task_id ON crm_campaign_events(task_id);

-- Index to support large-scale campaign event queries.
CREATE INDEX idx_campaign_events_campaign_updated ON crm_campaign_events (campaign_id, updated DESC);

-- customer:company connections (sharing a customer)

CREATE TABLE customer_company (
    company_id UUID NOT NULL,
    customer_id UUID NOT NULL,
    CONSTRAINT customer_company_pkey PRIMARY KEY (company_id, customer_id)
);


-- customer contacts (sharing a contact with multiple customers)

CREATE TABLE customer_contacts (
    accountd BOOLEAN DEFAULT true,
    accountd_to BOOLEAN DEFAULT false,
    accountd_cc BOOLEAN DEFAULT false,
    contact_id UUID NOT NULL,
    customer_id UUID NOT NULL,
    location TEXT,
    servicd BOOLEAN DEFAULT true,
    servicd_to BOOLEAN DEFAULT false,
    servicd_cc BOOLEAN DEFAULT false,
    stockd BOOLEAN DEFAULT true,
    stockd_to BOOLEAN DEFAULT false,
    stockd_cc BOOLEAN DEFAULT false,
    CONSTRAINT customer_contacts_pkey PRIMARY KEY (contact_id, customer_id)
);


-- customer email defaults

CREATE TABLE customer_email_defaults (
    accountd BOOLEAN DEFAULT true,
    contact_id UUID NOT NULL,
    customer_id UUID NOT NULL,
    level SMALLINT NOT NULL DEFAULT 1,
    location TEXT,
    servicd BOOLEAN DEFAULT true,
    stockd BOOLEAN DEFAULT true,
    CONSTRAINT customer_email_defaults_pkey PRIMARY KEY (contact_id, customer_id)
);


-- customer accounts

CREATE TABLE customers (
    account_number  CHARACTER VARYING(30) NOT NULL,
    accountd        BOOLEAN DEFAULT false,
    active          BOOLEAN DEFAULT true,
    company_id      UUID NOT NULL REFERENCES companies(company_id) ON DELETE RESTRICT,
    customer_id     UUID,
    customer_name   CHARACTER VARYING(60) NOT NULL,
    notes           TEXT,
    sales_tax       BOOLEAN DEFAULT false,
    servicd         BOOLEAN DEFAULT false,
    stockd          BOOLEAN DEFAULT false,
    terms_default   UUID,
    timezone        TEXT,
    UNIQUE (company_id, customer_name),

    CONSTRAINT customers_pkey PRIMARY KEY (customer_id)    
);


-- customer locations

CREATE TABLE customer_locations (
    location_id   UUID PRIMARY KEY,
    customer_id   UUID NOT NULL REFERENCES customers(customer_id) ON DELETE CASCADE,
    active        BOOLEAN NOT NULL DEFAULT true,
    address       TEXT,
    contact_info  TEXT,
    location      TEXT NOT NULL,
    notes         TEXT,
    timezone      TEXT,

    CONSTRAINT customer_locations_pkey PRIMARY KEY (location_id),
    CONSTRAINT customer_locations_customer_id FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE RESTRICT

);


-- service billing

CREATE TABLE customer_service (
    service_id UUID NOT NULL,
    customer_id UUID NOT NULL REFERENCES customers(customer_id) ON DELETE CASCADE,
    labor_1 NUMERIC (6,2) NOT NULL DEFAULT 0,
    labor_2 NUMERIC (6,2) NOT NULL DEFAULT 0,
    labor_3 NUMERIC (6,2) NOT NULL DEFAULT 0,
    travel_1 NUMERIC (6,2) NOT NULL DEFAULT 0,
    travel_2 NUMERIC (6,2) NOT NULL DEFAULT 0,
    travel_3 NUMERIC (6,2) NOT NULL DEFAULT 0,
    notes TEXT,
    locked NUMERIC(1,0) NOT NULL DEFAULT 0,
    service_name TEXT NOT NULL,
    updated BIGINT NOT NULL,
    updated_by UUID NOT NULL,
    CONSTRAINT customer_service_pkey PRIMARY KEY (service_id),
    CONSTRAINT customer_service_customer_id FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE RESTRICT
);


-- documents

CREATE TABLE documents (
    caption TEXT NOT NULL,
    company_id UUID NOT NULL,
    created BIGINT NOT NULL,
    description TEXT,
    document_id UUID NOT NULL,
    file_name TEXT NOT NULL,
    file_size BIGINT,
    file_type CHARACTER VARYING(30) NOT NULL,
    public BOOLEAN DEFAULT false,
    CONSTRAINT documents_pkey PRIMARY KEY (document_id)
);


-- email log contacts

CREATE TABLE email_log_contacts (
    contact_id UUID NOT NULL,
    email_id UUID NOT NULL,
    main BOOLEAN NOT NULL,
    CONSTRAINT email_log_contacts_pkey PRIMARY KEY (contact_id, email_id)
);


-- email log metadata

CREATE TABLE email_log_metadata (
    attachment BOOLEAN DEFAULT false,
    email_id UUID NOT NULL,
    email_text TEXT NOT NULL,
    item_id UUID NOT NULL,
    item_type NUMERIC(2,0) NOT NULL,
    reply_to TEXT NOT NULL,
    sender TEXT NOT NULL,
    sent BIGINT NOT NULL,
    subject TEXT NOT NULL,
    CONSTRAINT email_log_metadata_pkey PRIMARY KEY (email_id)
);


-- email schedule records

CREATE TABLE email_schedule (
    attachment BOOLEAN DEFAULT false,
    cc_contacts TEXT,
    company_id UUID NOT NULL,
    draft BOOLEAN DEFAULT false,
    email_id UUID NOT NULL,
    email_text TEXT NOT NULL,
    item_id UUID NOT NULL,
    item_type NUMERIC(2,0) NOT NULL,
    reply_to TEXT,
    send BIGINT NOT NULL,
    signature UUID,
    subject TEXT NOT NULL,
    to_contacts TEXT,
    CONSTRAINT email_schedule_pkey PRIMARY KEY (email_id)
);


-- email signatures

CREATE TABLE email_signatures (
    html BOOLEAN DEFAULT false,
    locked SMALLINT,
    owner UUID NOT NULL,
    private BOOLEAN DEFAULT true,
    signature_id UUID NOT NULL,
    signature_name CHARACTER VARYING(100) NOT NULL,
    signature_text TEXT NOT NULL,
    CONSTRAINT email_signatures_pkey PRIMARY KEY (signature_id),
    CONSTRAINT email_signatures_signature_name_key UNIQUE (signature_name)
);


-- expense templates

CREATE TABLE expense_templates (
    active BOOLEAN DEFAULT true,
    amount NUMERIC(11,2) NOT NULL DEFAULT 0,
    company_id UUID NOT NULL,
    credit_card UUID,
    default_user UUID NOT NULL,
    description TEXT,
    expense_type UUID NOT NULL,
    expires BIGINT,
    interval SMALLINT,
    locked SMALLINT NOT NULL DEFAULT(0),
    next_time BIGINT,
    posting_account UUID,
    recurring BOOLEAN DEFAULT false,
    starts BIGINT,
    template_id UUID NOT NULL,
    template_name CHARACTER VARYING(100) NOT NULL,
    vendor_id UUID,
    CONSTRAINT expense_templates_pkey PRIMARY KEY (template_id)
);


-- expense types

CREATE TABLE expense_types (
    active BOOLEAN DEFAULT true,
    company_id UUID NOT NULL,
    gaap NUMERIC(5,0),
    posts_to UUID NOT NULL,
    type_id UUID NOT NULL,
    type_name TEXT NOT NULL,
    CONSTRAINT expense_types_pkey PRIMARY KEY (type_id),
    CONSTRAINT expense_types_company_id_type_name_key UNIQUE (company_id, type_name)
);


-- expenses

CREATE TABLE expenses (
    amount NUMERIC(11,2) NOT NULL DEFAULT 0,
    company_id UUID NOT NULL,
    credit_card UUID,
    date DATE NOT NULL,
    description TEXT,
    expense_id UUID NOT NULL,
    expense_number NUMERIC(7,0) NOT NULL,
    expense_type UUID NOT NULL,
    locked SMALLINT,
    pending BOOLEAN DEFAULT false,
    posting_account UUID NOT NULL,
    user_id UUID NOT NULL,
    vendor_id UUID,
    CONSTRAINT expenses_pkey PRIMARY KEY (expense_id),
    CONSTRAINT expenses_expense_number_key UNIQUE (expense_number)
);


-- external access

CREATE TABLE external_access (
    access_name TEXT NOT NULL UNIQUE,
    company_id UUID NOT NULL,
    external_client UUID NOT NULL,
    token TEXT UNIQUE NOT NULL,
    group_id UUID NOT NULL,
    user_id UUID NOT NULL,
    CONSTRAINT external_access_key UNIQUE (access_name, company_id, user_id)
);


-- what item classes can access this field group?

CREATE TABLE fields_access(
  category        NUMERIC(2,0) NOT NULL,                  -- grant access to this field group (for inventory, quotes, ...in the item classes)
  field_group     UUID NOT NULL,                          -- the field group
  locked          SMALLINT NOT NULL DEFAULT(1),           -- level required to change it
  UNIQUE(category, field_group)
);


-- recording actual custom fields entries

CREATE TABLE fields_data(
  boolean_value   BOOLEAN,                                -- only for field type 3
  field_id        UUID NOT NULL,                          -- must exist in fields_metadata.field_id (the custom type)
  field_type      SMALLINT NOT NULL,                      -- must match fields_metadata.field_type (the custom type's field type)
  number_value    NUMERIC(14,3),                          -- only for field type 2
  target_id       UUID NOT NULL,                          -- the item (in the real world) using this field and value
  text_value      TEXT,                                   -- only for field type 1
  uuid_value      UUID,                                   -- only for field type 4
  value_id        UUID NOT NULL,                          -- must exist in fields_values for the supplied field_id

  -- Constraint 1: Ensure only one value column is populated
  CONSTRAINT only_one_value CHECK (
    (boolean_value IS NOT NULL)::int +
    (number_value IS NOT NULL)::int +
    (text_value IS NOT NULL)::int +
    (uuid_value IS NOT NULL)::int = 1
  ),

  -- Constraint 2: Prevent duplicate (field_id, target_id, value)
  CONSTRAINT unique_value_per_item UNIQUE (
    field_id, target_id, boolean_value, number_value, text_value, uuid_value
  ),

  -- Constraint 3: Prevent multiple value types for the same (field_id, target_id)
  CONSTRAINT unique_type_per_target UNIQUE (
    field_id, target_id, field_type, value_id
  )
  
);


-- custom field groups

CREATE TABLE fields_groups(
  active          BOOLEAN NOT NULL DEFAULT(true),         -- is this group available for use?
  field_group     UUID NOT NULL,                          -- unique ID for this group of 1 or more fields,
  group_name      TEXT NOT NULL,                          -- the group name (i.e. "models")
  locked          SMALLINT NOT NULL DEFAULT(1),       -- level required to change it
  updated         BIGINT NOT NULL,                 -- last update
  updated_by      UUID NOT NULL                           -- who updated it
);


-- custom fields metadata (within groups)

CREATE TABLE fields_metadata(
  active          BOOLEAN NOT NULL DEFAULT(true),         -- is this field available for use?
  default_value   UUID,                                   -- select a value from field_values as the default
  field_group     UUID NOT NULL,                          -- the field group
  field_id        UUID NOT NULL,                          -- unique ID for this type of field
  field_name      TEXT UNIQUE NOT NULL,                   -- unique field name
  field_type      SMALLINT NOT NULL,                  -- 1 = text, 2 = number, 3 = boolean, 4 = UUID (1/2 are text, 3 is checkbox, 4 is selector)
  gui_type        SMALLINT NOT NULL DEFAULT(1),       -- 1 = selector, 2 = checkbox, 3 = text, 4 = radio
  locked          SMALLINT NOT NULL DEFAULT(1),       -- level required to change it.
  placeholder     TEXT,                                   -- when this set, the field should show a placeholder (select/text only)
  updated         BIGINT NOT NULL,                 -- last update
  updated_by      UUID NOT NULL                           -- who updated it
);


-- custom fields values (presets) for a defined field

CREATE TABLE fields_values(
  active          BOOLEAN NOT NULL DEFAULT(true),         -- is this field available for use (it can be retired for new items but retained for display)
  field_id        UUID NOT NULL,                          -- which field is this a value for?
  field_value     TEXT NOT NULL,                          -- how is this option labeled?
  locked          SMALLINT NOT NULL DEFAULT(1),       -- level required to change it
  position        NUMERIC(2,0) NOT NULL,                  -- order of options within this value set (1 field_id, multiple options)
  updated         BIGINT NOT NULL,                 -- last update
  updated_by      UUID NOT NULL,                          -- who updated it,
  value_id        UUID NOT NULL,                          -- unique ID for this field value
  UNIQUE(field_id,field_value)
);


-- history records

CREATE TABLE history (
    event NUMERIC(3,0),
    event_time BIGINT NOT NULL,
    event_id UUID NOT NULL,
    item_id UUID NOT NULL,
    item_type NUMERIC(2,0) NOT NULL,
    notes TEXT,
    user_id UUID NOT NULL,
    CONSTRAINT history_pkey PRIMARY KEY (event_id)
);
CREATE INDEX history_item_id_idx ON history (item_id);
CREATE INDEX history_item_id_type_idx ON history (item_id, item_type);
CREATE INDEX history_event_time_idx ON history (event_time);

-- inventory items

CREATE TABLE inventory_items (
    category SMALLINT DEFAULT 1,
    company_id UUID,
    condition NUMERIC(2,0) NOT NULL,
    exchange_item UUID,
    item_id UUID NOT NULL,
    item_name TEXT NOT NULL,
    item_number NUMERIC(7,0) NOT NULL,
    leader UUID,
    location UUID NOT NULL,
    quantity NUMERIC(4,0) NOT NULL,
    reserved BOOLEAN DEFAULT false,
    sell BOOLEAN DEFAULT true,
    serial_number CHARACTER VARYING(50),
    sku CHARACTER VARYING(60) NOT NULL,
    source_id UUID,
    stocked BIGINT,
    verified BIGINT,
    CONSTRAINT inventory_items_pkey PRIMARY KEY (item_id),
    CONSTRAINT inventory_items_item_number_key UNIQUE (item_number)
);


-- Job info records

CREATE TABLE job_info (
    job_id            UUID                NOT NULL,
    active            BOOLEAN             DEFAULT true,
    assigned_user     UUID,
    billable          BOOLEAN             DEFAULT true,
    company_id        UUID                NOT NULL,
    customer_id       UUID                NOT NULL,
    customer_location UUID,
    customer_po       VARCHAR(60),
    job_notes         TEXT,
    job_number        SMALLINT,
    job_reason        TEXT                NOT NULL DEFAULT 'Job reason has not been defined yet.',
    job_state         SMALLINT            DEFAULT 1,
    locked            SMALLINT            NOT NULL DEFAULT 0,
    posting_account   UUID,

    -- Constraints
    CONSTRAINT job_info_pkey PRIMARY KEY (job_id),
    CONSTRAINT job_info_job_number_key UNIQUE (job_number),
    CONSTRAINT fk_company FOREIGN KEY (company_id) REFERENCES companies(company_id) ON DELETE RESTRICT,
    CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE RESTRICT,
    CONSTRAINT fk_assigner FOREIGN KEY (assigned_user) REFERENCES users(user_id) ON DELETE SET NULL
);


-- Job cost records

CREATE TABLE job_costs (
    cost_id UUID NOT NULL,
    cost_position SMALLINT NOT NULL,
    job_id UUID NOT NULL,
    cost_type UUID NOT NULL,
    cost_amount NUMERIC(8,2) NOT NULL DEFAULT 0,
    cost_date NUMERIC(10,0) NOT NULL,
    billable BOOLEAN DEFAULT false,
    billable_amount NUMERIC(8,2) NOT NULL DEFAULT 0,
    description TEXT NOT NULL,
    locked SMALLINT NOT NULL DEFAULT 0,
    UNIQUE(job_id, cost_position),


    -- Primary Key
    CONSTRAINT job_costs_pkey PRIMARY KEY (cost_id),

    -- Foreign Keys
    CONSTRAINT fk_cost_job FOREIGN KEY (job_id) REFERENCES job_info(job_id) ON DELETE CASCADE,
    CONSTRAINT fk_cost_type FOREIGN KEY (cost_type) REFERENCES expense_types(type_id) ON DELETE RESTRICT
);


-- Job item records

CREATE TABLE job_items (
    billable        BOOLEAN DEFAULT false,
    cost            NUMERIC(8,2),
    description     TEXT NOT NULL,
    inventory_id    UUID,
    item_id         UUID NOT NULL,
    item_name       TEXT NOT NULL,
    item_number     NUMERIC(7,0),
    job_id          UUID NOT NULL,
    locked          SMALLINT NOT NULL DEFAULT 0,
    quantity        NUMERIC(4,0) NOT NULL DEFAULT 1,
    serial_number   VARCHAR(50),
    sku             VARCHAR(60),
    UNIQUE(job_id, item_number),

    -- Primary Key
    CONSTRAINT job_items_pkey PRIMARY KEY (item_id),

    -- Foreign Keys
    CONSTRAINT fk_job FOREIGN KEY (job_id) REFERENCES job_info(job_id) ON DELETE CASCADE
);


-- Job tickets

CREATE TABLE job_tickets (
    arrived            NUMERIC(10,0),
    assigned_user      UUID NOT NULL,
    booked             NUMERIC(10,0) NOT NULL,
    billable_expenses  NUMERIC(11,2),
    billable_labor_1   NUMERIC(4,1),
    billable_labor_2   NUMERIC(4,1),
    billable_labor_3   NUMERIC(4,1),
    billable_travel_1  NUMERIC(4,1),
    billable_travel_2  NUMERIC(4,1),
    billable_travel_3  NUMERIC(4,1),
    fixed              NUMERIC(10,0),
    finished           NUMERIC(10,0),
    job_id             UUID NOT NULL,
    job_status         NUMERIC(2,0) NOT NULL DEFAULT 1,
    labor_hours        NUMERIC(4,2),
    locked             NUMERIC(1,0) NOT NULLDEFAULT 0,
    private_notes      TEXT,
    reason             TEXT NOT NULL,
    suppressed         BOOLEAN DEFAULT false,
    ticket_id          UUID NOT NULL,
    travel_hours       NUMERIC(4,2),
    work_performed     TEXT,

    -- Primary Key
    CONSTRAINT job_tickets_pkey PRIMARY KEY (ticket_id),

    -- Foreign Keys
    CONSTRAINT fk_job FOREIGN KEY (job_id) REFERENCES job_info(job_id) ON DELETE CASCADE
);


-- Sales line items

CREATE TABLE line_items (
    active BOOLEAN DEFAULT true,
    company_id UUID NOT NULL,
    description TEXT,
    item_id UUID NOT NULL,
    item_name TEXT NOT NULL,
    posting_account UUID NOT NULL,
    CONSTRAINT line_items_pkey PRIMARY KEY (item_id)
);

-- Links

CREATE TABLE links (
    active BOOLEAN DEFAULT true,
    item_company_id UUID NOT NULL,
    item_id UUID NOT NULL,
    link_company_id UUID NOT NULL,
    link_id UUID NOT NULL,
    link_item_id UUID NOT NULL,
    link_type NUMERIC(2,0) NOT NULL,
    timestamp BIGINT,
    CONSTRAINT links_pkey PRIMARY KEY (link_id),
    CONSTRAINT unique_item_link UNIQUE (item_id, link_item_id)
);


-- Messages

CREATE TABLE messages (
    content TEXT NOT NULL,
    message_id UUID NOT NULL,
    sent_by UUID NOT NULL,
    sent_time BIGINT NOT NULL,
    target UUID NOT NULL,
    CONSTRAINT messages_pkey PRIMARY KEY (message_id)
);

-- Notes

CREATE TABLE notes (
    author UUID NOT NULL,
    created CHARACTER VARYING(10) NOT NULL,
    note_id UUID NOT NULL,
    notes TEXT NOT NULL,
    updated CHARACTER VARYING(10) NOT NULL,
    updated_by UUID NOT NULL,
    CONSTRAINT notes_pkey PRIMARY KEY (note_id)
);


-- Note connections

CREATE TABLE notes_connections (
    connection_id UUID NOT NULL,
    connection_type NUMERIC(2,0) NOT NULL,
    note_id UUID NOT NULL,
    CONSTRAINT notes_connections_pkey PRIMARY KEY (connection_id, note_id)
);

-- Sales order items

CREATE TABLE order_items (
    billable BOOLEAN DEFAULT true,
    created BIGINT,
    description TEXT,
    inventory_item UUID,
    line_item UUID,
    order_id UUID,
    order_item_id UUID NOT NULL,
    quantity NUMERIC(5,0),
    quote_item_id UUID,
    rate1 NUMERIC(11,2),
    rate2 NUMERIC(11,2),
    sku CHARACTER VARYING(60),
    updated BIGINT,
    CONSTRAINT order_items_pkey PRIMARY KEY (order_item_id)
);


-- Sales orders 

CREATE TABLE orders (
    additional_billing BOOLEAN DEFAULT false,
    billable BOOLEAN DEFAULT true,
    billed BOOLEAN DEFAULT false,
    company_id UUID NOT NULL,
    customer_po CHARACTER VARYING(30),
    created BIGINT NOT NULL,
    customer_id UUID NOT NULL,
    locked SMALLINT,
    order_date DATE,
    order_id UUID NOT NULL,
    order_label VARCHAR(30),
    order_name TEXT NOT NULL,
    order_number NUMERIC(6,0) NOT NULL UNIQUE,
    order_status NUMERIC(2,0) NOT NULL,
    quote_id UUID,
    updated BIGINT NOT NULL,
    user_id UUID NOT NULL
);


-- Invoices requires both job_info and orders to be defined first

BEGIN;

------------------------------------------------------------
-- 1.  TABLE: invoices
--      Depends on: public.orders, public.job_info
------------------------------------------------------------

CREATE TABLE public.invoices (
    --------------------------------------------------------
    -- CORE FIELDS
    --------------------------------------------------------
    invoice_id       UUID            NOT NULL,
    invoice_number   NUMERIC(8,2)    NOT NULL,
    company_id       UUID            NOT NULL,
    customer_id      UUID            NOT NULL,
    created          NUMERIC(10,0)   NOT NULL,
    updated          NUMERIC(10,0)   NOT NULL,

    --------------------------------------------------------
    -- RELATIONSHIPS
    --------------------------------------------------------
    order_id         UUID,
    job_id           UUID,

    --------------------------------------------------------
    -- DATES
    --------------------------------------------------------
    invoice_date     DATE            NOT NULL,
    invoice_due      DATE            NOT NULL,

    --------------------------------------------------------
    -- CUSTOMER INFO
    --------------------------------------------------------
    customer_po      VARCHAR(60),
    customer_notes   TEXT,

    --------------------------------------------------------
    -- FLAGS
    --------------------------------------------------------
    collectible      BOOLEAN         DEFAULT TRUE,
    non_posting      BOOLEAN         DEFAULT FALSE,
    locked           NUMERIC(1,0)    DEFAULT 0,

    --------------------------------------------------------
    -- CONSTRAINTS
    --------------------------------------------------------
    CONSTRAINT invoices_pkey PRIMARY KEY (invoice_id),

    CONSTRAINT invoices_invoice_number_key
        UNIQUE (invoice_number),

    CONSTRAINT invoices_order_id_fkey
        FOREIGN KEY (order_id)
        REFERENCES public.orders(order_id)
        ON UPDATE CASCADE
        ON DELETE SET NULL,

    CONSTRAINT invoices_job_id_fkey
        FOREIGN KEY (job_id)
        REFERENCES public.job_info(job_id)
        ON UPDATE CASCADE
        ON DELETE SET NULL
);

------------------------------------------------------------
-- 2.  INDEXES FOR invoices
------------------------------------------------------------
CREATE INDEX idx_invoices_company_id   ON public.invoices(company_id);
CREATE INDEX idx_invoices_customer_id  ON public.invoices(customer_id);
CREATE INDEX idx_invoices_order_id     ON public.invoices(order_id);
CREATE INDEX idx_invoices_job_id       ON public.invoices(job_id);
CREATE INDEX idx_invoices_invoice_date ON public.invoices(invoice_date);
CREATE INDEX idx_invoices_invoice_due  ON public.invoices(invoice_due);

------------------------------------------------------------
-- 3.  TABLE: invoice_items
--      Depends on: public.invoices
------------------------------------------------------------

CREATE TABLE public.invoice_items (
    --------------------------------------------------------
    -- CORE FIELDS
    --------------------------------------------------------
    invoice_item     UUID            NOT NULL,
    invoice_id       UUID            NOT NULL,
    position         NUMERIC(3,0)    NOT NULL,

    --------------------------------------------------------
    -- ITEM DETAILS
    --------------------------------------------------------
    description      TEXT            NOT NULL,
    notes            TEXT,
    exchange_required BOOLEAN        DEFAULT FALSE,
    posting_account  UUID            NOT NULL,
    line_item        UUID            NOT NULL,
    quantity         NUMERIC(8,2)    NOT NULL,
    rate1            NUMERIC(11,2)   NOT NULL,
    rate2            NUMERIC(11,2)   NOT NULL,

    --------------------------------------------------------
    -- CONSTRAINTS
    --------------------------------------------------------
    CONSTRAINT invoice_items_pkey PRIMARY KEY (invoice_item),

    CONSTRAINT invoice_items_invoice_id_fkey
        FOREIGN KEY (invoice_id)
        REFERENCES public.invoices(invoice_id)
        ON UPDATE CASCADE
        ON DELETE CASCADE
);

------------------------------------------------------------
-- 4.  INDEXES FOR invoice_items
------------------------------------------------------------
CREATE INDEX idx_invoice_items_invoice_id  ON public.invoice_items(invoice_id);
CREATE INDEX idx_invoice_items_posting_acct ON public.invoice_items(posting_account);
CREATE INDEX idx_invoice_items_position     ON public.invoice_items(position);
CREATE INDEX idx_invoice_items_line_item    ON public.invoice_items(line_item);

COMMIT;


-- Payments

CREATE TABLE payments (
    created BIGINT NOT NULL,
    company_id UUID NOT NULL,
    deposit_account UUID NOT NULL,
    locked SMALLINT DEFAULT 0,
    payment_date DATE NOT NULL,
    payment_id UUID NOT NULL,
    updated BIGINT NOT NULL,
    CONSTRAINT payments_pkey PRIMARY KEY (payment_id)
);


-- Sales quote items

CREATE TABLE quote_items (
    description TEXT,
    item_id UUID,
    position NUMERIC(3,0),
    quantity NUMERIC(5,2),
    quote_id UUID,
    rate1 NUMERIC(9,2),
    rate2 NUMERIC(9,2),
    sku CHARACTER VARYING(30),
    CONSTRAINT quote_items_pkey PRIMARY KEY (quote_id, item_id)
);


-- Sales quotes metadata

CREATE TABLE quotes (
    active BOOLEAN DEFAULT true,
    company_id UUID NOT NULL,
    created BIGINT NOT NULL,
    expires BIGINT NOT NULL,
    locked SMALLINT,
    prepared_for UUID NOT NULL,
    quote_id UUID NOT NULL,
    quote_number NUMERIC(6,0) NOT NULL,
    rating SMALLINT,
    terms TEXT,
    updated BIGINT NOT NULL,
    CONSTRAINT unique_quote_id_quote_number UNIQUE (quote_id, quote_number),
    CONSTRAINT quotes_pkey PRIMARY KEY (quote_id)
);


-- Reconciliation records

CREATE TABLE reconciled (
    account UUID NOT NULL,
    date DATE NOT NULL,
    statement NUMERIC(11,2),
    CONSTRAINT reconciled_pkey PRIMARY KEY (account, date)
);


-- Sales requests

CREATE TABLE requests (
    company_id UUID NOT NULL,
    contact_id UUID,
    customer_id UUID,
    external BOOLEAN DEFAULT false,
    healthica_id UUID,
    item_name CHARACTER VARYING(100) NOT NULL,
    manufacturer UUID,
    modality UUID,
    request_id UUID NOT NULL,
    sku CHARACTER VARYING(60) NOT NULL,
    status NUMERIC(2,0) NOT NULL,
    timestamp BIGINT NOT NULL,
    type NUMERIC(2,0) NOT NULL,
    CONSTRAINT requests_pkey PRIMARY KEY (request_id)
);


-- Return items (RMAs)

CREATE TABLE return_items (
    created BIGINT NOT NULL,
    due DATE NOT NULL,
    item_status SMALLINT NOT NULL DEFAULT 1,
    order_id UUID NOT NULL,
    original_item UUID,
    return_id UUID NOT NULL,
    returned_item UUID,
    updated BIGINT NOT NULL,
    CONSTRAINT return_items_pkey PRIMARY KEY (return_id),
    CONSTRAINT return_items_original_item_order_id_key UNIQUE (original_item, order_id)
);


-- Return records (RMAs)

CREATE TABLE returns (
    open boolean NOT NULL DEFAULT true,
    order_id UUID NOT NULL,
    CONSTRAINT returns_order_id_key UNIQUE (order_id)
);


-- SKU policies

CREATE OR REPLACE FUNCTION clean_alternates(text)
RETURNS text[] LANGUAGE sql IMMUTABLE STRICT AS $$
  SELECT ARRAY(
    SELECT trim(s)
    FROM unnest(string_to_array($1, '||')) s
  );
$$;

CREATE TABLE sku_policies (
    alternates text,
    GENERATED ALWAYS AS (clean_alternates(alternates)) STORED,
    cost_floor BIGINT NOT NULL,
    deprecated BOOLEAN DEFAULT false,
    exchange NUMERIC(10,2) NOT NULL,
    notes TEXT,
    outright NUMERIC(10,2) NOT NULL,
    policy_id UUID NOT NULL,
    reviewed BIGINT NOT NULL,
    sku TEXT NOT NULL,
    sku_name TEXT NOT NULL,
    CONSTRAINT sku_policies_pkey PRIMARY KEY (policy_id),
    CONSTRAINT sku_policies_sku_key UNIQUE (sku)
);


-- Stocking locations

CREATE TABLE stocking_locations (
    active BOOLEAN DEFAULT true,
    location_id UUID NOT NULL,
    location_name CHARACTER VARYING(60) NOT NULL,
    location_type SMALLINT NOT NULL DEFAULT 1,
    CONSTRAINT stocking_locations_pkey PRIMARY KEY (location_id),
    CONSTRAINT stocking_locations_location_id_key UNIQUE (location_id)
);


-- Tag connections

CREATE TABLE tag_connections (
    class NUMERIC(2,0) NOT NULL,
    item_id UUID NOT NULL,
    tag_id UUID NOT NULL,
    CONSTRAINT tag_connections_pkey PRIMARY KEY (class, item_id, tag_id)
);


-- Tags

CREATE TABLE tags (
    active BOOLEAN DEFAULT true,
    notes TEXT,
    tag_id UUID NOT NULL,
    tag_text CHARACTER VARYING(30) NOT NULL,
    CONSTRAINT tags_pkey PRIMARY KEY (tag_id)
);


-- Terms and conditions

CREATE TABLE terms (
    active BOOLEAN DEFAULT true,
    terms_id UUID NOT NULL,
    terms_title TEXT NOT NULL,
    terms_text TEXT NOT NULL,
    CONSTRAINT terms_pkey PRIMARY KEY (terms_id),
    CONSTRAINT terms_terms_title_key UNIQUE (terms_title)
);


-- Test bypass records

CREATE TABLE test_bypass (
    company_id UUID NOT NULL,
    item_id UUID NOT NULL,
    item_type NUMERIC(2,0) NOT NULL,
    test_id NUMERIC(3,0) NOT NULL,
    user_id UUID NOT NULL,
    CONSTRAINT test_bypass_pkey PRIMARY KEY (item_id, test_id)
);


-- Transactions lines

CREATE TABLE transaction_lines (
    account UUID NOT NULL,
    amount NUMERIC(11,2) NOT NULL,
    cleared BOOLEAN DEFAULT false,
    debit BOOLEAN DEFAULT false,
    pointer UUID,
    statement DATE,
    transaction_number NUMERIC(6,0) NOT NULL
);


-- Transaction metadata

CREATE TABLE transaction_metadata (
    category SMALLINT NOT NULL DEFAULT 1,
    cogs BOOLEAN DEFAULT false,
    date DATE NOT NULL,
    date2 DATE NOT NULL,
    locked SMALLINT DEFAULT 0,
    reconciled BOOLEAN DEFAULT false,
    transaction_id UUID,
    transaction_number NUMERIC(6,0),
    type NUMERIC(2,0),
    CONSTRAINT transaction_metadata_category_check CHECK (category >= 1 AND category <= 5),
    CONSTRAINT transaction_metadata_transaction_number_check CHECK (transaction_number > 0)
);


-- User settings

CREATE TABLE users (
    adhoc_updated NUMERIC(10, 0) DEFAULT 0, -- the last time Adhoc was updated by anyone
    adhoc_viewed BIGINT DEFAULT 0, -- last time this user opened Adhoc
    commissions UUID, -- some users will have a designated commissions clearing account
    email_signature UUID, -- current email signature
    timezone CHARACTER VARYING(30) NOT NULL DEFAULT 'US/Pacific',
    user_id UUID NOT NULL, -- must match the ID assigned by Services API
    CONSTRAINT users_user_id_key UNIQUE (user_id)
);


-- Vendor-company connections

CREATE TABLE vendor_company (
    company_id UUID NOT NULL,
    vendor_id UUID NOT NULL,
    CONSTRAINT vendor_company_pkey PRIMARY KEY (company_id, vendor_id)
);


-- Vendor-contact connections

CREATE TABLE vendor_contacts (
    accountd BOOLEAN DEFAULT true,
    accountd_to BOOLEAN DEFAULT false,
    accountd_cc BOOLEAN DEFAULT false,
    contact_id UUID NOT NULL,
    servicd BOOLEAN DEFAULT true,
    servicd_to BOOLEAN DEFAULT false,
    servicd_cc BOOLEAN DEFAULT false,
    stockd BOOLEAN DEFAULT true,
    stockd_to BOOLEAN DEFAULT false,
    stockd_cc BOOLEAN DEFAULT false,
    vendor_id UUID NOT NULL,
    CONSTRAINT vendor_contacts_pkey PRIMARY KEY (contact_id, vendor_id)
);


-- Vendors

CREATE TABLE vendors(
    accountd BOOLEAN DEFAULT true,
    active BOOLEAN DEFAULT true,
    company_id UUID NOT NULL,
    net_due NUMERIC(3,0),
    notes TEXT,
    servicd BOOLEAN DEFAULT true,
    stockd BOOLEAN DEFAULT true,
    vendor_id UUID NOT NULL,
    vendor_name TEXT NOT NULL,
    CONSTRAINT vendors_pkey PRIMARY KEY (vendor_id)
);

-- Reusable view to allow searches with or without company access records

CREATE VIEW accessible_companies AS
SELECT
  c.company_id,
  c.company_name,
  c.restricted,
  ca.user_id
FROM
  companies c
LEFT JOIN
  company_access ca ON c.company_id = ca.company_id;