9e519f5c8f
- Add database migration: contacts, conversations, messages, leads, workflows, follow-ups, audit log - Contact handler with deduplication and identity resolution - Conversation handler with unified inbox - Lead handler with scoring engine and qualification - Workflow handler with trigger-action engine - Full API routes for all comm layer endpoints - 12 operational agents integrated in all BOC modules
235 lines
9.1 KiB
SQL
235 lines
9.1 KiB
SQL
-- 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);
|