Files
boc/db/001_initial_schema.sql
T
Bernt (LandveX AI) 95b581e8c5 fix(security): JWT require env, remove *** token, WS auth disabled
fix(automation): implement all 6 actions + real cron parser
fix(db): pq.Array for TEXT[], add sqlmock tests
fix(schema): single source migrations
docs: v2 architecture + frontend refactor proposals
2026-07-14 11:46:06 +00:00

526 lines
22 KiB
PL/PgSQL

-- BOC Initial Schema
-- Business Operations Center — Full schema for all modules
-- Created: 2026-07-12
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Core: Tenants (multi-tenant support)
CREATE TABLE IF NOT EXISTS boc_tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
domain TEXT,
settings JSONB DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Core: Users
CREATE TABLE IF NOT EXISTS boc_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
email TEXT NOT NULL,
name TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
avatar_url TEXT,
settings JSONB DEFAULT '{}',
last_login TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tenant_id, email)
);
-- Core: Audit log (immutable)
CREATE TABLE IF NOT EXISTS boc_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
user_id UUID REFERENCES boc_users(id),
action TEXT NOT NULL,
entity_type TEXT NOT NULL,
entity_id TEXT NOT NULL,
old_value JSONB,
new_value JSONB,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- CRM: Customers
CREATE TABLE IF NOT EXISTS boc_customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
email TEXT,
phone TEXT,
company TEXT,
org_number TEXT,
address JSONB,
status TEXT NOT NULL DEFAULT 'lead',
source TEXT,
tags TEXT[] DEFAULT '{}',
metadata JSONB DEFAULT '{}',
assigned_to UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- CRM: Customer interactions
CREATE TABLE IF NOT EXISTS boc_customer_interactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
customer_id UUID REFERENCES boc_customers(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- call, email, meeting, note, task
direction TEXT, -- inbound, outbound
subject TEXT,
content TEXT,
metadata JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- CRM: Contacts (people within customer orgs)
CREATE TABLE IF NOT EXISTS boc_contacts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
customer_id UUID REFERENCES boc_customers(id) ON DELETE CASCADE,
name TEXT NOT NULL,
email TEXT,
phone TEXT,
title TEXT,
is_primary BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Sales: Deals
CREATE TABLE IF NOT EXISTS boc_deals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
customer_id UUID REFERENCES boc_customers(id),
contact_id UUID REFERENCES boc_contacts(id),
name TEXT NOT NULL,
description TEXT,
value DECIMAL(15,2) NOT NULL DEFAULT 0,
currency TEXT NOT NULL DEFAULT 'USD',
status TEXT NOT NULL DEFAULT 'open',
stage TEXT NOT NULL DEFAULT 'prospect',
probability INTEGER NOT NULL DEFAULT 0,
expected_close DATE,
actual_close TIMESTAMPTZ,
won_reason TEXT,
lost_reason TEXT,
tags TEXT[] DEFAULT '{}',
metadata JSONB DEFAULT '{}',
assigned_to UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Sales: Deal timeline / activities
CREATE TABLE IF NOT EXISTS boc_deal_activities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
deal_id UUID REFERENCES boc_deals(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- call, email, meeting, proposal, note, stage_change
description TEXT,
metadata JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Sales: Products/Services
CREATE TABLE IF NOT EXISTS boc_products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
sku TEXT,
price DECIMAL(15,2) NOT NULL DEFAULT 0,
currency TEXT NOT NULL DEFAULT 'USD',
unit TEXT DEFAULT 'piece',
is_recurring BOOLEAN DEFAULT FALSE,
billing_period TEXT, -- monthly, quarterly, yearly
status TEXT NOT NULL DEFAULT 'active',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Sales: Deal line items
CREATE TABLE IF NOT EXISTS boc_deal_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
deal_id UUID REFERENCES boc_deals(id) ON DELETE CASCADE,
product_id UUID REFERENCES boc_products(id),
quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
unit_price DECIMAL(15,2) NOT NULL DEFAULT 0,
discount DECIMAL(5,2) DEFAULT 0,
total DECIMAL(15,2) NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Finance: Invoices
CREATE TABLE IF NOT EXISTS boc_invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
customer_id UUID REFERENCES boc_customers(id),
deal_id UUID REFERENCES boc_deals(id),
invoice_number TEXT NOT NULL,
amount DECIMAL(15,2) NOT NULL DEFAULT 0,
tax_amount DECIMAL(15,2) DEFAULT 0,
currency TEXT NOT NULL DEFAULT 'USD',
status TEXT NOT NULL DEFAULT 'draft',
due_date DATE,
paid_at TIMESTAMPTZ,
paid_amount DECIMAL(15,2) DEFAULT 0,
notes TEXT,
metadata JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Finance: Invoice items
CREATE TABLE IF NOT EXISTS boc_invoice_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
invoice_id UUID REFERENCES boc_invoices(id) ON DELETE CASCADE,
description TEXT NOT NULL,
quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
unit_price DECIMAL(15,2) NOT NULL DEFAULT 0,
tax_rate DECIMAL(5,2) DEFAULT 0,
total DECIMAL(15,2) NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Finance: Payments
CREATE TABLE IF NOT EXISTS boc_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
invoice_id UUID REFERENCES boc_invoices(id),
amount DECIMAL(15,2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD',
method TEXT, -- bank_transfer, card, cash, stripe, etc
reference TEXT,
status TEXT NOT NULL DEFAULT 'completed',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Finance: Expenses
CREATE TABLE IF NOT EXISTS boc_expenses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
category TEXT NOT NULL,
description TEXT NOT NULL,
amount DECIMAL(15,2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD',
vendor TEXT,
receipt_url TEXT,
status TEXT NOT NULL DEFAULT 'pending',
approved_by UUID REFERENCES boc_users(id),
approved_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Finance: Budgets
CREATE TABLE IF NOT EXISTS boc_budgets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
fiscal_year INTEGER NOT NULL,
category TEXT,
amount DECIMAL(15,2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD',
spent DECIMAL(15,2) DEFAULT 0,
status TEXT NOT NULL DEFAULT 'active',
metadata JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- HR: Employees
CREATE TABLE IF NOT EXISTS boc_employees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
user_id UUID REFERENCES boc_users(id),
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
department TEXT,
position TEXT,
employment_type TEXT DEFAULT 'full_time',
salary DECIMAL(15,2),
currency TEXT DEFAULT 'USD',
start_date DATE,
end_date DATE,
manager_id UUID REFERENCES boc_employees(id),
address JSONB,
bank_info JSONB,
documents JSONB DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- HR: Time off / Leave
CREATE TABLE IF NOT EXISTS boc_leaves (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
employee_id UUID REFERENCES boc_employees(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- vacation, sick, parental, unpaid
start_date DATE NOT NULL,
end_date DATE NOT NULL,
days DECIMAL(4,1) NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
approved_by UUID REFERENCES boc_users(id),
approved_at TIMESTAMPTZ,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- HR: Timesheets
CREATE TABLE IF NOT EXISTS boc_timesheets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
employee_id UUID REFERENCES boc_employees(id) ON DELETE CASCADE,
date DATE NOT NULL,
hours DECIMAL(4,2) NOT NULL DEFAULT 0,
project TEXT,
task TEXT,
description TEXT,
status TEXT NOT NULL DEFAULT 'draft',
approved_by UUID REFERENCES boc_users(id),
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tenant_id, employee_id, date)
);
-- Legal: Contracts
CREATE TABLE IF NOT EXISTS boc_contracts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
title TEXT NOT NULL,
counterparty TEXT NOT NULL,
type TEXT NOT NULL, -- service, employment, nda, partnership, etc
status TEXT NOT NULL DEFAULT 'draft',
value DECIMAL(15,2),
currency TEXT DEFAULT 'USD',
start_date DATE,
end_date DATE,
renewal_date DATE,
document_url TEXT,
metadata JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Legal: Contract reminders
CREATE TABLE IF NOT EXISTS boc_contract_reminders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
contract_id UUID REFERENCES boc_contracts(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- renewal, expiration, payment, review
due_date DATE NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Marketing: Campaigns
CREATE TABLE IF NOT EXISTS boc_campaigns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
type TEXT NOT NULL, -- email, social, content, event, ad
status TEXT NOT NULL DEFAULT 'draft',
budget DECIMAL(15,2),
spent DECIMAL(15,2) DEFAULT 0,
currency TEXT DEFAULT 'USD',
start_date DATE,
end_date DATE,
metrics JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Marketing: Content items
CREATE TABLE IF NOT EXISTS boc_content (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
campaign_id UUID REFERENCES boc_campaigns(id),
title TEXT NOT NULL,
type TEXT NOT NULL, -- blog, social, email, video, whitepaper
status TEXT NOT NULL DEFAULT 'draft',
publish_at TIMESTAMPTZ,
published_at TIMESTAMPTZ,
url TEXT,
metrics JSONB DEFAULT '{}',
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Support: Tickets
CREATE TABLE IF NOT EXISTS boc_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
customer_id UUID REFERENCES boc_customers(id),
contact_id UUID REFERENCES boc_contacts(id),
subject TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'open',
priority TEXT NOT NULL DEFAULT 'medium',
category TEXT,
source TEXT, -- email, chat, phone, web
assigned_to UUID REFERENCES boc_users(id),
resolved_at TIMESTAMPTZ,
resolution TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Support: Ticket comments
CREATE TABLE IF NOT EXISTS boc_ticket_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
ticket_id UUID REFERENCES boc_tickets(id) ON DELETE CASCADE,
content TEXT NOT NULL,
is_internal BOOLEAN DEFAULT FALSE,
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Automation: Workflows
CREATE TABLE IF NOT EXISTS boc_workflows (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
trigger_type TEXT NOT NULL, -- schedule, event, webhook, manual
trigger_config JSONB DEFAULT '{}',
actions JSONB NOT NULL DEFAULT '[]',
status TEXT NOT NULL DEFAULT 'active',
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
run_count INTEGER DEFAULT 0,
fail_count INTEGER DEFAULT 0,
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Automation: Workflow runs
CREATE TABLE IF NOT EXISTS boc_workflow_runs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
workflow_id UUID REFERENCES boc_workflows(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'running',
input JSONB DEFAULT '{}',
output JSONB DEFAULT '{}',
error TEXT,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- Automation: Scheduled jobs
CREATE TABLE IF NOT EXISTS boc_scheduled_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
cron_expr TEXT NOT NULL,
timezone TEXT DEFAULT 'UTC',
job_type TEXT NOT NULL, -- report, reminder, sync, cleanup, backup
job_config JSONB DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active',
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
run_count INTEGER DEFAULT 0,
fail_count INTEGER DEFAULT 0,
created_by UUID REFERENCES boc_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Automation: Scheduled job runs
CREATE TABLE IF NOT EXISTS boc_scheduled_job_runs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES boc_tenants(id) ON DELETE CASCADE,
job_id UUID REFERENCES boc_scheduled_jobs(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'running',
output JSONB DEFAULT '{}',
error TEXT,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_audit_tenant ON boc_audit_log(tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_customers_tenant ON boc_customers(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_customers_assigned ON boc_customers(assigned_to);
CREATE INDEX IF NOT EXISTS idx_interactions_customer ON boc_customer_interactions(customer_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_deals_tenant ON boc_deals(tenant_id, status, stage);
CREATE INDEX IF NOT EXISTS idx_deals_customer ON boc_deals(customer_id);
CREATE INDEX IF NOT EXISTS idx_deals_assigned ON boc_deals(assigned_to);
CREATE INDEX IF NOT EXISTS idx_deals_expected_close ON boc_deals(expected_close);
CREATE INDEX IF NOT EXISTS idx_invoices_tenant ON boc_invoices(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_invoices_due ON boc_invoices(due_date);
CREATE INDEX IF NOT EXISTS idx_expenses_tenant ON boc_expenses(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_employees_tenant ON boc_employees(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_leaves_employee ON boc_leaves(employee_id, start_date);
CREATE INDEX IF NOT EXISTS idx_timesheets_employee ON boc_timesheets(employee_id, date);
CREATE INDEX IF NOT EXISTS idx_contracts_tenant ON boc_contracts(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_contracts_renewal ON boc_contracts(renewal_date);
CREATE INDEX IF NOT EXISTS idx_tickets_tenant ON boc_tickets(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON boc_tickets(assigned_to);
CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON boc_workflows(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_scheduled_jobs_tenant ON boc_scheduled_jobs(tenant_id, status);
-- Functions
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Triggers for updated_at
DO $$
BEGIN
CREATE TRIGGER update_boc_tenants_updated_at BEFORE UPDATE ON boc_tenants FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_users_updated_at BEFORE UPDATE ON boc_users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_customers_updated_at BEFORE UPDATE ON boc_customers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_deals_updated_at BEFORE UPDATE ON boc_deals FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_invoices_updated_at BEFORE UPDATE ON boc_invoices FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_expenses_updated_at BEFORE UPDATE ON boc_expenses FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_budgets_updated_at BEFORE UPDATE ON boc_budgets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_employees_updated_at BEFORE UPDATE ON boc_employees FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_leaves_updated_at BEFORE UPDATE ON boc_leaves FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_timesheets_updated_at BEFORE UPDATE ON boc_timesheets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_contracts_updated_at BEFORE UPDATE ON boc_contracts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_campaigns_updated_at BEFORE UPDATE ON boc_campaigns FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_content_updated_at BEFORE UPDATE ON boc_content FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_tickets_updated_at BEFORE UPDATE ON boc_tickets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_workflows_updated_at BEFORE UPDATE ON boc_workflows FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_boc_scheduled_jobs_updated_at BEFORE UPDATE ON boc_scheduled_jobs FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
EXCEPTION WHEN duplicate_object THEN
-- Triggers already exist, ignore
END;
$$;