-- Communication & Lead Layer - Core Tables -- Migration: 2025081201 -- Contact Identity - central contact model CREATE TABLE IF NOT EXISTS contacts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, first_name VARCHAR(100), last_name VARCHAR(100), email VARCHAR(255), phone VARCHAR(50), whatsapp_id VARCHAR(100), instagram_id VARCHAR(100), facebook_id VARCHAR(100), external_id VARCHAR(100), company VARCHAR(255), org_number VARCHAR(50), tags TEXT[], status VARCHAR(50) DEFAULT 'active', lead_score INTEGER DEFAULT 0, source VARCHAR(100), metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), merged_into UUID REFERENCES contacts(id), UNIQUE(tenant_id, email), UNIQUE(tenant_id, phone) ); -- Contact Identity Links - for deduplication CREATE TABLE IF NOT EXISTS contact_identity_links ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), contact_id UUID NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, channel VARCHAR(50) NOT NULL, -- 'whatsapp', 'email', 'phone', 'instagram', etc. channel_id VARCHAR(255) NOT NULL, confidence FLOAT DEFAULT 1.0, verified BOOLEAN DEFAULT false, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(contact_id, channel, channel_id) ); -- Conversations CREATE TABLE IF NOT EXISTS conversations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, contact_id UUID NOT NULL REFERENCES contacts(id), channel VARCHAR(50) NOT NULL, channel_conversation_id VARCHAR(255), status VARCHAR(50) DEFAULT 'new', -- 'new', 'active', 'waiting', 'resolved', 'escalated' assigned_to UUID, assigned_team VARCHAR(100), lead_id UUID, tags TEXT[], priority VARCHAR(20) DEFAULT 'normal', -- 'low', 'normal', 'high', 'urgent' last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), next_action_at TIMESTAMP WITH TIME ZONE, next_action_type VARCHAR(100), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), metadata JSONB DEFAULT '{}' ); -- Messages CREATE TABLE IF NOT EXISTS messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, contact_id UUID NOT NULL REFERENCES contacts(id), direction VARCHAR(20) NOT NULL, -- 'inbound', 'outbound' content TEXT NOT NULL, content_type VARCHAR(50) DEFAULT 'text', -- 'text', 'image', 'file', 'template' channel_message_id VARCHAR(255), sender_type VARCHAR(50) DEFAULT 'contact', -- 'contact', 'agent', 'system', 'user' sender_id UUID, read_at TIMESTAMP WITH TIME ZONE, delivered_at TIMESTAMP WITH TIME ZONE, metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Leads CREATE TABLE IF NOT EXISTS leads ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, contact_id UUID NOT NULL REFERENCES contacts(id), conversation_id UUID REFERENCES conversations(id), source VARCHAR(100) NOT NULL, source_metadata JSONB DEFAULT '{}', status VARCHAR(50) DEFAULT 'new', -- 'new', 'contacted', 'qualifying', 'qualified', 'sales', 'won', 'lost' interest VARCHAR(255), product VARCHAR(255), urgency VARCHAR(20) DEFAULT 'normal', -- 'low', 'normal', 'high', 'urgent' customer_type VARCHAR(50), -- 'new', 'existing' owner UUID, team VARCHAR(100), lead_score INTEGER DEFAULT 0, qualification_state JSONB DEFAULT '{}', qualification_complete BOOLEAN DEFAULT false, last_interaction_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), next_action_at TIMESTAMP WITH TIME ZONE, next_action_type VARCHAR(100), won_at TIMESTAMP WITH TIME ZONE, lost_at TIMESTAMP WITH TIME ZONE, lost_reason VARCHAR(255), tags TEXT[], metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Lead Score History CREATE TABLE IF NOT EXISTS lead_score_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), lead_id UUID NOT NULL REFERENCES leads(id) ON DELETE CASCADE, score INTEGER NOT NULL, previous_score INTEGER, reason TEXT NOT NULL, factors JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Tags CREATE TABLE IF NOT EXISTS tags ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, name VARCHAR(100) NOT NULL, color VARCHAR(20) DEFAULT '#2563EB', category VARCHAR(100), description TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(tenant_id, name) ); -- Workflows CREATE TABLE IF NOT EXISTS workflows ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, trigger VARCHAR(100) NOT NULL, -- 'new_contact', 'new_lead', 'tag_added', 'status_changed', etc. trigger_config JSONB DEFAULT '{}', conditions JSONB DEFAULT '[]', actions JSONB DEFAULT '[]', status VARCHAR(50) DEFAULT 'active', -- 'active', 'paused', 'draft' execution_count INTEGER DEFAULT 0, last_executed_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Workflow Executions CREATE TABLE IF NOT EXISTS workflow_executions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), workflow_id UUID NOT NULL REFERENCES workflows(id), trigger_event VARCHAR(100) NOT NULL, trigger_data JSONB DEFAULT '{}', status VARCHAR(50) DEFAULT 'running', -- 'running', 'completed', 'failed', 'waiting' current_step INTEGER DEFAULT 0, steps JSONB DEFAULT '[]', result JSONB DEFAULT '{}', error TEXT, started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), completed_at TIMESTAMP WITH TIME ZONE ); -- Follow-ups CREATE TABLE IF NOT EXISTS follow_ups ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, lead_id UUID REFERENCES leads(id), conversation_id UUID REFERENCES conversations(id), type VARCHAR(100) NOT NULL, description TEXT, assigned_to UUID, due_at TIMESTAMP WITH TIME ZONE NOT NULL, completed_at TIMESTAMP WITH TIME ZONE, status VARCHAR(50) DEFAULT 'pending', -- 'pending', 'completed', 'overdue', 'cancelled' reminder_sent BOOLEAN DEFAULT false, metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Audit Log CREATE TABLE IF NOT EXISTS communication_audit_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, actor_type VARCHAR(50) NOT NULL, -- 'user', 'system', 'agent', 'workflow' actor_id UUID, action VARCHAR(100) NOT NULL, object_type VARCHAR(100) NOT NULL, -- 'contact', 'lead', 'conversation', 'message', 'workflow' object_id UUID NOT NULL, previous_state JSONB, new_state JSONB, source VARCHAR(100), correlation_id UUID, ip_address INET, user_agent TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Indexes CREATE INDEX IF NOT EXISTS idx_contacts_tenant ON contacts(tenant_id); CREATE INDEX IF NOT EXISTS idx_contacts_email ON contacts(email); CREATE INDEX IF NOT EXISTS idx_contacts_phone ON contacts(phone); CREATE INDEX IF NOT EXISTS idx_contacts_merged ON contacts(merged_into); CREATE INDEX IF NOT EXISTS idx_contact_links_contact ON contact_identity_links(contact_id); CREATE INDEX IF NOT EXISTS idx_contact_links_channel ON contact_identity_links(channel, channel_id); CREATE INDEX IF NOT EXISTS idx_conversations_tenant ON conversations(tenant_id); CREATE INDEX IF NOT EXISTS idx_conversations_contact ON conversations(contact_id); CREATE INDEX IF NOT EXISTS idx_conversations_status ON conversations(status); CREATE INDEX IF NOT EXISTS idx_conversations_assigned ON conversations(assigned_to); CREATE INDEX IF NOT EXISTS idx_conversations_lead ON conversations(lead_id); CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id); CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at); CREATE INDEX IF NOT EXISTS idx_leads_tenant ON leads(tenant_id); CREATE INDEX IF NOT EXISTS idx_leads_contact ON leads(contact_id); CREATE INDEX IF NOT EXISTS idx_leads_status ON leads(status); CREATE INDEX IF NOT EXISTS idx_leads_owner ON leads(owner); CREATE INDEX IF NOT EXISTS idx_leads_score ON leads(lead_score DESC); CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant_id); CREATE INDEX IF NOT EXISTS idx_workflows_trigger ON workflows(trigger); CREATE INDEX IF NOT EXISTS idx_followups_lead ON follow_ups(lead_id); CREATE INDEX IF NOT EXISTS idx_followups_due ON follow_ups(due_at); CREATE INDEX IF NOT EXISTS idx_followups_status ON follow_ups(status); CREATE INDEX IF NOT EXISTS idx_audit_tenant ON communication_audit_log(tenant_id); CREATE INDEX IF NOT EXISTS idx_audit_object ON communication_audit_log(object_type, object_id); CREATE INDEX IF NOT EXISTS idx_audit_correlation ON communication_audit_log(correlation_id); CREATE INDEX IF NOT EXISTS idx_audit_created ON communication_audit_log(created_at);