feat: Implement Phase 3 & 4 - AI Reports & ERP Integrations
## Phase 3: DeepSeek AI Integration ### AI Services (apps/api/src/services/ai/) - DeepSeek API client with streaming, rate limiting, retries - AI service for financial analysis, executive summaries, recommendations - Optimized prompts for Mexican CFO digital assistant - Redis caching for AI responses ### Report Generation (apps/api/src/services/reports/) - ReportGenerator: monthly, quarterly, annual, custom reports - PDF generator with corporate branding (PDFKit) - Report templates for PYME, Startup, Enterprise - Spanish prompts for financial narratives - MinIO storage for generated PDFs ### AI & Reports API Routes - POST /api/ai/analyze - Financial metrics analysis - POST /api/ai/chat - CFO digital chat with streaming - POST /api/reports/generate - Async report generation - GET /api/reports/:id/download - PDF download - BullMQ jobs for background processing ### Frontend Pages - /reportes - Report listing with filters - /reportes/nuevo - 3-step wizard for report generation - /reportes/[id] - Full report viewer with charts - /asistente - CFO Digital chat interface - Components: ChatInterface, ReportCard, AIInsightCard ## Phase 4: ERP Integrations ### CONTPAQi Connector (SQL Server) - Contabilidad: catalog, polizas, balanza, estado de resultados - Comercial: clientes, proveedores, facturas, inventario - Nominas: empleados, nominas, percepciones/deducciones ### Aspel Connector (Firebird/SQL Server) - COI: accounting, polizas, balanza, auxiliares - SAE: sales, purchases, inventory, A/R, A/P - NOI: payroll, employees, receipts - BANCO: bank accounts, movements, reconciliation - Latin1 to UTF-8 encoding handling ### Odoo Connector (XML-RPC) - Accounting: chart of accounts, journal entries, reports - Invoicing: customer/vendor invoices, payments - Partners: customers, vendors, statements - Inventory: products, stock levels, valuations - Multi-company and version 14-17 support ### Alegra Connector (REST API) - Invoices, credit/debit notes with CFDI support - Contacts with Mexican fiscal fields (RFC, regimen) - Payments and bank reconciliation - Financial reports: trial balance, P&L, balance sheet - Webhook support for real-time sync ### SAP Business One Connector (OData/Service Layer) - Session management with auto-refresh - Financials: chart of accounts, journal entries, reports - Sales: invoices, credit notes, delivery notes, orders - Purchasing: bills, POs, goods receipts - Inventory: items, stock, transfers, valuation - Banking: accounts, payments, cash flow ### Integration Manager - Unified interface for all connectors - BullMQ scheduler for automated sync - Webhook handling for real-time updates - Migration 003_integrations.sql with sync tables - Frontend page /integraciones for configuration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
642
packages/database/src/migrations/003_integrations.sql
Normal file
642
packages/database/src/migrations/003_integrations.sql
Normal file
@@ -0,0 +1,642 @@
|
||||
-- ============================================================================
|
||||
-- Horux Strategy - Integrations Schema
|
||||
-- Version: 003
|
||||
-- Description: Tables for external integrations management
|
||||
-- Note: ${SCHEMA_NAME} will be replaced with the actual schema name
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- ENUM TYPES
|
||||
-- ============================================================================
|
||||
|
||||
-- Integration type
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'integration_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE integration_type AS ENUM (
|
||||
'contpaqi',
|
||||
'aspel',
|
||||
'odoo',
|
||||
'alegra',
|
||||
'sap',
|
||||
'manual',
|
||||
'sat',
|
||||
'bank_bbva',
|
||||
'bank_banamex',
|
||||
'bank_santander',
|
||||
'bank_banorte',
|
||||
'bank_hsbc',
|
||||
'payments_stripe',
|
||||
'payments_openpay',
|
||||
'webhook',
|
||||
'api_custom'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Integration status
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'integration_status' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE integration_status AS ENUM (
|
||||
'pending',
|
||||
'active',
|
||||
'inactive',
|
||||
'error',
|
||||
'expired',
|
||||
'configuring'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Sync status
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'sync_status' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE sync_status AS ENUM (
|
||||
'pending',
|
||||
'queued',
|
||||
'running',
|
||||
'completed',
|
||||
'failed',
|
||||
'cancelled',
|
||||
'partial'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Sync direction
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'sync_direction' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE sync_direction AS ENUM (
|
||||
'import',
|
||||
'export',
|
||||
'bidirectional'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Sync entity type
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'sync_entity_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE sync_entity_type AS ENUM (
|
||||
'transactions',
|
||||
'invoices',
|
||||
'contacts',
|
||||
'products',
|
||||
'accounts',
|
||||
'categories',
|
||||
'journal_entries',
|
||||
'payments',
|
||||
'cfdis',
|
||||
'bank_statements'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- ============================================================================
|
||||
-- INTEGRATIONS TABLE
|
||||
-- Main table for integration configurations
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS integrations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Integration info
|
||||
type integration_type NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Status
|
||||
status integration_status NOT NULL DEFAULT 'pending',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Configuration (encrypted JSON)
|
||||
config JSONB NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Connection health
|
||||
last_health_check_at TIMESTAMP WITH TIME ZONE,
|
||||
health_status VARCHAR(20), -- healthy, degraded, unhealthy, unknown
|
||||
health_message TEXT,
|
||||
|
||||
-- Last sync info
|
||||
last_sync_at TIMESTAMP WITH TIME ZONE,
|
||||
last_sync_status sync_status,
|
||||
last_sync_error TEXT,
|
||||
next_sync_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Statistics
|
||||
total_syncs INTEGER NOT NULL DEFAULT 0,
|
||||
successful_syncs INTEGER NOT NULL DEFAULT 0,
|
||||
failed_syncs INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- Audit
|
||||
created_by UUID NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT integrations_unique_type UNIQUE (type) WHERE type NOT IN ('webhook', 'api_custom') AND deleted_at IS NULL
|
||||
);
|
||||
|
||||
-- Indexes for integrations
|
||||
CREATE INDEX IF NOT EXISTS idx_integrations_type ON integrations(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_integrations_status ON integrations(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_integrations_active ON integrations(is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_integrations_next_sync ON integrations(next_sync_at) WHERE next_sync_at IS NOT NULL AND is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_integrations_deleted ON integrations(deleted_at) WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- SYNC_JOBS TABLE
|
||||
-- Individual sync job executions
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sync_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Job info
|
||||
job_type VARCHAR(100) NOT NULL,
|
||||
status sync_status NOT NULL DEFAULT 'pending',
|
||||
|
||||
-- Timing
|
||||
started_at TIMESTAMP WITH TIME ZONE,
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Progress
|
||||
progress INTEGER NOT NULL DEFAULT 0, -- 0-100
|
||||
|
||||
-- Results
|
||||
records_processed INTEGER DEFAULT 0,
|
||||
records_created INTEGER DEFAULT 0,
|
||||
records_updated INTEGER DEFAULT 0,
|
||||
records_failed INTEGER DEFAULT 0,
|
||||
|
||||
-- Parameters
|
||||
parameters JSONB,
|
||||
result_summary JSONB,
|
||||
|
||||
-- Error handling
|
||||
error_message TEXT,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
max_retries INTEGER DEFAULT 3,
|
||||
next_retry_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Audit
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for sync_jobs
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_jobs_integration ON sync_jobs(integration_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_jobs_status ON sync_jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_jobs_created ON sync_jobs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_jobs_active ON sync_jobs(integration_id, status) WHERE status IN ('pending', 'queued', 'running');
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_jobs_retry ON sync_jobs(next_retry_at) WHERE status = 'failed' AND retry_count < max_retries;
|
||||
|
||||
-- ============================================================================
|
||||
-- INTEGRATION_SYNC_LOGS TABLE
|
||||
-- Detailed sync history and metrics
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS integration_sync_logs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,
|
||||
job_id UUID REFERENCES sync_jobs(id) ON DELETE SET NULL,
|
||||
|
||||
-- Sync details
|
||||
entity_type sync_entity_type,
|
||||
direction sync_direction NOT NULL DEFAULT 'import',
|
||||
status sync_status NOT NULL,
|
||||
|
||||
-- Timing
|
||||
started_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
duration_ms INTEGER,
|
||||
|
||||
-- Results
|
||||
total_records INTEGER DEFAULT 0,
|
||||
created_records INTEGER DEFAULT 0,
|
||||
updated_records INTEGER DEFAULT 0,
|
||||
skipped_records INTEGER DEFAULT 0,
|
||||
failed_records INTEGER DEFAULT 0,
|
||||
|
||||
-- Errors
|
||||
error_count INTEGER DEFAULT 0,
|
||||
last_error TEXT,
|
||||
|
||||
-- Trigger info
|
||||
triggered_by VARCHAR(20) NOT NULL DEFAULT 'manual', -- schedule, manual, webhook, system
|
||||
triggered_by_user_id UUID,
|
||||
|
||||
-- Metadata
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for integration_sync_logs
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_logs_integration ON integration_sync_logs(integration_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_logs_job ON integration_sync_logs(job_id) WHERE job_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_logs_created ON integration_sync_logs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_logs_status ON integration_sync_logs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_logs_entity ON integration_sync_logs(entity_type) WHERE entity_type IS NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- INTEGRATION_LOGS TABLE
|
||||
-- General integration event logs (connections, tests, etc.)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS integration_logs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Event info
|
||||
integration_type integration_type NOT NULL,
|
||||
integration_id UUID REFERENCES integrations(id) ON DELETE SET NULL,
|
||||
event_type VARCHAR(50) NOT NULL, -- connection_test, config_change, error, etc.
|
||||
status VARCHAR(20) NOT NULL, -- success, failed, warning
|
||||
|
||||
-- Details
|
||||
message TEXT,
|
||||
latency_ms INTEGER,
|
||||
metadata JSONB,
|
||||
|
||||
-- Audit
|
||||
user_id UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for integration_logs
|
||||
CREATE INDEX IF NOT EXISTS idx_integration_logs_type ON integration_logs(integration_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_integration_logs_integration ON integration_logs(integration_id) WHERE integration_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_integration_logs_created ON integration_logs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_integration_logs_event ON integration_logs(event_type);
|
||||
|
||||
-- ============================================================================
|
||||
-- INTEGRATION_SCHEDULES TABLE
|
||||
-- Scheduled sync configurations
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS integration_schedules (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Schedule config
|
||||
entity_type sync_entity_type,
|
||||
direction sync_direction NOT NULL DEFAULT 'import',
|
||||
is_enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Cron expression
|
||||
cron_expression VARCHAR(100) NOT NULL,
|
||||
timezone VARCHAR(50) NOT NULL DEFAULT 'America/Mexico_City',
|
||||
|
||||
-- Execution window
|
||||
start_time VARCHAR(5), -- HH:mm
|
||||
end_time VARCHAR(5), -- HH:mm
|
||||
days_of_week JSONB, -- [0,1,2,3,4,5,6]
|
||||
|
||||
-- Execution info
|
||||
last_run_at TIMESTAMP WITH TIME ZONE,
|
||||
next_run_at TIMESTAMP WITH TIME ZONE,
|
||||
last_status sync_status,
|
||||
|
||||
-- Options
|
||||
priority VARCHAR(10) NOT NULL DEFAULT 'normal', -- low, normal, high
|
||||
timeout_ms INTEGER NOT NULL DEFAULT 300000, -- 5 minutes
|
||||
|
||||
-- Audit
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for integration_schedules
|
||||
CREATE INDEX IF NOT EXISTS idx_schedules_integration ON integration_schedules(integration_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedules_enabled ON integration_schedules(is_enabled) WHERE is_enabled = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_schedules_next_run ON integration_schedules(next_run_at) WHERE is_enabled = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- SYNC_MAPPINGS TABLE
|
||||
-- Field mappings between external systems and Horux
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sync_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Mapping info
|
||||
name VARCHAR(200) NOT NULL,
|
||||
entity_type sync_entity_type NOT NULL,
|
||||
source_entity VARCHAR(100) NOT NULL, -- Entity name in external system
|
||||
target_entity VARCHAR(100) NOT NULL, -- Entity name in Horux
|
||||
|
||||
-- Field mappings
|
||||
field_mappings JSONB NOT NULL DEFAULT '[]',
|
||||
/*
|
||||
Example:
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"sourceField": "CLAVECTE",
|
||||
"targetField": "external_id",
|
||||
"transformationType": "direct",
|
||||
"isRequired": true
|
||||
},
|
||||
{
|
||||
"id": "uuid",
|
||||
"sourceField": "NOMCTE",
|
||||
"targetField": "name",
|
||||
"transformationType": "direct",
|
||||
"isRequired": true
|
||||
},
|
||||
{
|
||||
"id": "uuid",
|
||||
"sourceField": "RFC",
|
||||
"targetField": "rfc",
|
||||
"transformationType": "formula",
|
||||
"transformationValue": "UPPER(TRIM(value))",
|
||||
"isRequired": false
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
-- Filters
|
||||
source_filters JSONB, -- Filters applied when fetching from source
|
||||
target_filters JSONB, -- Filters applied before saving to target
|
||||
|
||||
-- Options
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
skip_duplicates BOOLEAN NOT NULL DEFAULT true,
|
||||
update_existing BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Audit
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for sync_mappings
|
||||
CREATE INDEX IF NOT EXISTS idx_mappings_integration ON sync_mappings(integration_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_mappings_entity ON sync_mappings(entity_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_mappings_active ON sync_mappings(is_active) WHERE is_active = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- SYNC_VALUE_MAPPINGS TABLE
|
||||
-- Value lookups for field transformations
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sync_value_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
mapping_id UUID NOT NULL REFERENCES sync_mappings(id) ON DELETE CASCADE,
|
||||
|
||||
-- Mapping
|
||||
field_name VARCHAR(100) NOT NULL,
|
||||
source_value VARCHAR(500) NOT NULL,
|
||||
target_value VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Audit
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Unique constraint
|
||||
UNIQUE(mapping_id, field_name, source_value)
|
||||
);
|
||||
|
||||
-- Indexes for sync_value_mappings
|
||||
CREATE INDEX IF NOT EXISTS idx_value_mappings_mapping ON sync_value_mappings(mapping_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_value_mappings_field ON sync_value_mappings(field_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_value_mappings_lookup ON sync_value_mappings(mapping_id, field_name, source_value) WHERE is_active = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- SYNC_ERRORS TABLE
|
||||
-- Detailed error tracking for sync failures
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sync_errors (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
job_id UUID NOT NULL REFERENCES sync_jobs(id) ON DELETE CASCADE,
|
||||
log_id UUID REFERENCES integration_sync_logs(id) ON DELETE SET NULL,
|
||||
|
||||
-- Error details
|
||||
error_code VARCHAR(50) NOT NULL,
|
||||
error_message TEXT NOT NULL,
|
||||
source_id VARCHAR(255), -- ID in external system
|
||||
target_id UUID, -- ID in Horux
|
||||
field_name VARCHAR(100),
|
||||
|
||||
-- Context
|
||||
source_data JSONB,
|
||||
stack_trace TEXT,
|
||||
|
||||
-- Resolution
|
||||
is_resolved BOOLEAN NOT NULL DEFAULT false,
|
||||
resolved_at TIMESTAMP WITH TIME ZONE,
|
||||
resolved_by UUID,
|
||||
resolution_notes TEXT,
|
||||
|
||||
-- Retry info
|
||||
is_retryable BOOLEAN NOT NULL DEFAULT true,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
|
||||
-- Audit
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for sync_errors
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_errors_job ON sync_errors(job_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_errors_code ON sync_errors(error_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_errors_unresolved ON sync_errors(is_resolved, created_at DESC) WHERE is_resolved = false;
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_errors_source ON sync_errors(source_id) WHERE source_id IS NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- WEBHOOK_EVENTS TABLE
|
||||
-- Incoming webhook events from external systems
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webhook_events (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Event info
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
event_id VARCHAR(255), -- External event ID
|
||||
|
||||
-- Payload
|
||||
payload JSONB NOT NULL,
|
||||
headers JSONB,
|
||||
|
||||
-- Processing
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, processing, completed, failed
|
||||
processed_at TIMESTAMP WITH TIME ZONE,
|
||||
error_message TEXT,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
|
||||
-- Validation
|
||||
signature_valid BOOLEAN,
|
||||
|
||||
-- Audit
|
||||
received_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
ip_address INET
|
||||
);
|
||||
|
||||
-- Indexes for webhook_events
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_integration ON webhook_events(integration_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_type ON webhook_events(event_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_status ON webhook_events(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_pending ON webhook_events(integration_id, status) WHERE status = 'pending';
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_received ON webhook_events(received_at DESC);
|
||||
|
||||
-- ============================================================================
|
||||
-- EXTERNAL_ID_MAPPINGS TABLE
|
||||
-- Track relationships between Horux IDs and external system IDs
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS external_id_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
integration_id UUID NOT NULL REFERENCES integrations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Entity info
|
||||
entity_type sync_entity_type NOT NULL,
|
||||
horux_id UUID NOT NULL,
|
||||
external_id VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Metadata
|
||||
external_data JSONB, -- Store additional external info
|
||||
last_synced_at TIMESTAMP WITH TIME ZONE,
|
||||
sync_hash VARCHAR(64), -- Hash of last synced data for change detection
|
||||
|
||||
-- Audit
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Unique constraint
|
||||
UNIQUE(integration_id, entity_type, horux_id),
|
||||
UNIQUE(integration_id, entity_type, external_id)
|
||||
);
|
||||
|
||||
-- Indexes for external_id_mappings
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_mappings_integration ON external_id_mappings(integration_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_mappings_entity ON external_id_mappings(entity_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_mappings_horux ON external_id_mappings(horux_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_mappings_external ON external_id_mappings(external_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_mappings_lookup ON external_id_mappings(integration_id, entity_type, external_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- TRIGGERS
|
||||
-- ============================================================================
|
||||
|
||||
-- Update integrations updated_at
|
||||
CREATE TRIGGER update_integrations_updated_at BEFORE UPDATE ON integrations
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Update sync_jobs updated_at
|
||||
CREATE TRIGGER update_sync_jobs_updated_at BEFORE UPDATE ON sync_jobs
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Update integration_schedules updated_at
|
||||
CREATE TRIGGER update_schedules_updated_at BEFORE UPDATE ON integration_schedules
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Update sync_mappings updated_at
|
||||
CREATE TRIGGER update_mappings_updated_at BEFORE UPDATE ON sync_mappings
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Update external_id_mappings updated_at
|
||||
CREATE TRIGGER update_ext_mappings_updated_at BEFORE UPDATE ON external_id_mappings
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ============================================================================
|
||||
-- FUNCTIONS
|
||||
-- ============================================================================
|
||||
|
||||
-- Function to update integration stats after sync
|
||||
CREATE OR REPLACE FUNCTION update_integration_stats()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.status = 'completed' THEN
|
||||
UPDATE integrations
|
||||
SET
|
||||
last_sync_at = NEW.completed_at,
|
||||
last_sync_status = NEW.status,
|
||||
last_sync_error = NULL,
|
||||
total_syncs = total_syncs + 1,
|
||||
successful_syncs = successful_syncs + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = NEW.integration_id;
|
||||
ELSIF NEW.status = 'failed' THEN
|
||||
UPDATE integrations
|
||||
SET
|
||||
last_sync_at = COALESCE(NEW.completed_at, NOW()),
|
||||
last_sync_status = NEW.status,
|
||||
last_sync_error = NEW.error_message,
|
||||
total_syncs = total_syncs + 1,
|
||||
failed_syncs = failed_syncs + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = NEW.integration_id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger to update integration stats
|
||||
DROP TRIGGER IF EXISTS trigger_update_integration_stats ON sync_jobs;
|
||||
CREATE TRIGGER trigger_update_integration_stats
|
||||
AFTER UPDATE OF status ON sync_jobs
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.status IS DISTINCT FROM NEW.status AND NEW.status IN ('completed', 'failed'))
|
||||
EXECUTE FUNCTION update_integration_stats();
|
||||
|
||||
-- Function to clean old sync logs
|
||||
CREATE OR REPLACE FUNCTION cleanup_old_sync_logs(days_to_keep INTEGER DEFAULT 90)
|
||||
RETURNS INTEGER AS $$
|
||||
DECLARE
|
||||
deleted_count INTEGER;
|
||||
BEGIN
|
||||
DELETE FROM integration_sync_logs
|
||||
WHERE created_at < NOW() - (days_to_keep || ' days')::INTERVAL;
|
||||
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
||||
RETURN deleted_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Function to clean old webhook events
|
||||
CREATE OR REPLACE FUNCTION cleanup_old_webhook_events(days_to_keep INTEGER DEFAULT 30)
|
||||
RETURNS INTEGER AS $$
|
||||
DECLARE
|
||||
deleted_count INTEGER;
|
||||
BEGIN
|
||||
DELETE FROM webhook_events
|
||||
WHERE received_at < NOW() - (days_to_keep || ' days')::INTERVAL
|
||||
AND status IN ('completed', 'failed');
|
||||
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
||||
RETURN deleted_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================================================
|
||||
-- COMMENTS
|
||||
-- ============================================================================
|
||||
|
||||
COMMENT ON TABLE integrations IS 'External system integration configurations';
|
||||
COMMENT ON TABLE sync_jobs IS 'Individual sync job executions';
|
||||
COMMENT ON TABLE integration_sync_logs IS 'Detailed sync history and metrics';
|
||||
COMMENT ON TABLE integration_logs IS 'General integration event logs';
|
||||
COMMENT ON TABLE integration_schedules IS 'Scheduled sync configurations';
|
||||
COMMENT ON TABLE sync_mappings IS 'Field mappings between external systems and Horux';
|
||||
COMMENT ON TABLE sync_value_mappings IS 'Value lookups for field transformations';
|
||||
COMMENT ON TABLE sync_errors IS 'Detailed error tracking for sync failures';
|
||||
COMMENT ON TABLE webhook_events IS 'Incoming webhook events from external systems';
|
||||
COMMENT ON TABLE external_id_mappings IS 'Track relationships between Horux and external IDs';
|
||||
|
||||
-- ============================================================================
|
||||
-- DEFAULT DATA
|
||||
-- ============================================================================
|
||||
|
||||
-- Insert default sync mappings templates (can be customized per tenant)
|
||||
-- These would typically be inserted when a new integration is created
|
||||
Reference in New Issue
Block a user