feat(comm): implement Communication & Lead Layer core
- 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
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
-- 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);
|
||||
Reference in New Issue
Block a user