Files
boc/backend/db/migrations/2025081201_communication_layer.sql
T
Bernt 9e519f5c8f
BOC CI/CD / Test (push) Failing after 2s
BOC CI/CD / Security Scan (push) Has been skipped
BOC CI/CD / Build & Push (push) Has been skipped
BOC CI/CD / Deploy to Staging (push) Has been skipped
BOC CI/CD / Deploy to Production (push) Has been skipped
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
2026-08-12 10:15:54 +00:00

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);