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:
2026-01-31 11:25:17 +00:00
parent a9b1994c48
commit 45570baccc
88 changed files with 52538 additions and 531 deletions

View 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