from __future__ import annotations from typing import Any from fastapi import HTTPException from backend.auth.dependencies import default_tenant_id _CANONICAL_CRM_SCHEMA_CACHE_KEY = "_canonical_crm_schema_ready" def _sql_text_literal(value: str) -> str: return "'" + value.replace("'", "''") + "'" async def ensure_canonical_crm_schema(app: Any) -> None: if getattr(app.state, _CANONICAL_CRM_SCHEMA_CACHE_KEY, False): return pool = getattr(app.state, "db_pool", None) if pool is None: raise HTTPException(status_code=503, detail="Database unavailable.") tenant_fallback = default_tenant_id() tenant_default_literal = _sql_text_literal(tenant_fallback) tenant_tables = ( "crm_people", "crm_accounts", "crm_leads", "crm_opportunities", "crm_property_interests", "intel_interactions", "intel_reminders", "intel_qd_scores", "intel_qd_timeseries", "workflow_actions", "workflow_approvals", "workflow_import_batches", ) async with pool.acquire() as conn: for table_name in tenant_tables: await conn.execute(f"ALTER TABLE {table_name} ADD COLUMN IF NOT EXISTS tenant_id TEXT") await conn.execute( f""" UPDATE {table_name} SET tenant_id = $1 WHERE tenant_id IS NULL OR tenant_id = '' """, tenant_fallback, ) await conn.execute( f"ALTER TABLE {table_name} ALTER COLUMN tenant_id SET DEFAULT {tenant_default_literal}" ) await conn.execute(f"ALTER TABLE {table_name} ALTER COLUMN tenant_id SET NOT NULL") await conn.execute( "CREATE INDEX IF NOT EXISTS idx_crm_people_tenant_created ON crm_people (tenant_id, created_at DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_crm_leads_tenant_status ON crm_leads (tenant_id, status, updated_at DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_crm_opportunities_tenant_stage ON crm_opportunities (tenant_id, stage, updated_at DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_crm_property_interests_tenant_person ON crm_property_interests (tenant_id, person_id, created_at DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_intel_interactions_tenant_person ON intel_interactions (tenant_id, person_id, happened_at DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_intel_reminders_tenant_status ON intel_reminders (tenant_id, status, due_at)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_intel_qd_scores_tenant_person ON intel_qd_scores (tenant_id, person_id, score_type)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_intel_qd_timeseries_tenant_person ON intel_qd_timeseries (tenant_id, person_id, timestamp DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_workflow_actions_tenant_status ON workflow_actions (tenant_id, status, created_at DESC)" ) await conn.execute( "CREATE INDEX IF NOT EXISTS idx_workflow_import_batches_tenant_lifecycle ON workflow_import_batches (tenant_id, lifecycle, created_at DESC)" ) setattr(app.state, _CANONICAL_CRM_SCHEMA_CACHE_KEY, True)