feat: Implement Phase 1 & 2 - Full monorepo architecture
## Backend API (apps/api) - Express.js server with TypeScript - JWT authentication with access/refresh tokens - Multi-tenant middleware (schema per tenant) - Complete CRUD routes: auth, cfdis, transactions, contacts, categories, metrics, alerts - SAT integration: CFDI 4.0 XML parser, FIEL authentication - Metrics engine: 50+ financial metrics (Core, Startup, Enterprise) - Rate limiting, CORS, Helmet security ## Frontend Web (apps/web) - Next.js 14 with App Router - Authentication pages: login, register, forgot-password - Dashboard layout with Sidebar and Header - Dashboard pages: overview, cash-flow, revenue, expenses, metrics - Zustand stores for auth and UI state - Theme support with flash prevention ## Database Package (packages/database) - PostgreSQL migrations with multi-tenant architecture - Public schema: plans, tenants, users, sessions, subscriptions - Tenant schema: sat_credentials, cfdis, transactions, contacts, accounts, alerts - Tenant management functions - Seed data for plans and super admin ## Shared Package (packages/shared) - TypeScript types: auth, tenant, financial, metrics, reports - Zod validation schemas for all entities - Utility functions for formatting ## UI Package (packages/ui) - Chart components: LineChart, BarChart, AreaChart, PieChart - Data components: DataTable, MetricCard, KPICard, AlertBadge - PeriodSelector and Skeleton components ## Infrastructure - Docker Compose: PostgreSQL 15, Redis 7, MinIO, Mailhog - Makefile with 25+ development commands - Development scripts: dev-setup.sh, dev-down.sh - Complete .env.example template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
658
packages/database/src/migrations/001_public_schema.sql
Normal file
658
packages/database/src/migrations/001_public_schema.sql
Normal file
@@ -0,0 +1,658 @@
|
||||
-- ============================================================================
|
||||
-- Horux Strategy - Public Schema Migration
|
||||
-- Version: 001
|
||||
-- Description: Core tables for multi-tenant SaaS platform
|
||||
-- ============================================================================
|
||||
|
||||
-- Enable required extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- For text search
|
||||
|
||||
-- ============================================================================
|
||||
-- ENUM TYPES
|
||||
-- ============================================================================
|
||||
|
||||
-- User roles enum
|
||||
CREATE TYPE user_role AS ENUM (
|
||||
'super_admin', -- Platform administrator (Horux team)
|
||||
'owner', -- Tenant owner (company owner)
|
||||
'admin', -- Tenant administrator
|
||||
'manager', -- Department manager
|
||||
'analyst', -- Financial analyst (read + limited write)
|
||||
'viewer' -- Read-only access
|
||||
);
|
||||
|
||||
-- Subscription status enum
|
||||
CREATE TYPE subscription_status AS ENUM (
|
||||
'active',
|
||||
'trial',
|
||||
'past_due',
|
||||
'cancelled',
|
||||
'suspended',
|
||||
'expired'
|
||||
);
|
||||
|
||||
-- Tenant status enum
|
||||
CREATE TYPE tenant_status AS ENUM (
|
||||
'active',
|
||||
'suspended',
|
||||
'pending',
|
||||
'deleted'
|
||||
);
|
||||
|
||||
-- Job status enum
|
||||
CREATE TYPE job_status AS ENUM (
|
||||
'pending',
|
||||
'running',
|
||||
'completed',
|
||||
'failed',
|
||||
'cancelled'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- PLANS TABLE
|
||||
-- Subscription plans available in the platform
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE plans (
|
||||
id VARCHAR(50) PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Pricing (in MXN cents to avoid floating point issues)
|
||||
price_monthly_cents INTEGER NOT NULL DEFAULT 0,
|
||||
price_yearly_cents INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- Limits
|
||||
max_users INTEGER NOT NULL DEFAULT 1,
|
||||
max_cfdis_monthly INTEGER NOT NULL DEFAULT 100,
|
||||
max_storage_mb INTEGER NOT NULL DEFAULT 1024,
|
||||
max_api_calls_daily INTEGER NOT NULL DEFAULT 1000,
|
||||
max_reports_monthly INTEGER NOT NULL DEFAULT 10,
|
||||
|
||||
-- Features (JSON for flexibility)
|
||||
features JSONB NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Feature flags
|
||||
has_sat_sync BOOLEAN NOT NULL DEFAULT false,
|
||||
has_bank_sync BOOLEAN NOT NULL DEFAULT false,
|
||||
has_ai_insights BOOLEAN NOT NULL DEFAULT false,
|
||||
has_custom_reports BOOLEAN NOT NULL DEFAULT false,
|
||||
has_api_access BOOLEAN NOT NULL DEFAULT false,
|
||||
has_white_label BOOLEAN NOT NULL DEFAULT false,
|
||||
has_priority_support BOOLEAN NOT NULL DEFAULT false,
|
||||
has_dedicated_account_manager BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Retention
|
||||
data_retention_months INTEGER NOT NULL DEFAULT 12,
|
||||
|
||||
-- Display
|
||||
display_order INTEGER NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
is_popular BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create index for active plans
|
||||
CREATE INDEX idx_plans_active ON plans(is_active, display_order);
|
||||
|
||||
-- ============================================================================
|
||||
-- TENANTS TABLE
|
||||
-- Companies/organizations using the platform
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE tenants (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Basic info
|
||||
name VARCHAR(255) NOT NULL,
|
||||
slug VARCHAR(255) NOT NULL UNIQUE,
|
||||
schema_name VARCHAR(100) NOT NULL UNIQUE,
|
||||
|
||||
-- Tax info (Mexican RFC)
|
||||
rfc VARCHAR(13),
|
||||
razon_social VARCHAR(500),
|
||||
|
||||
-- Contact
|
||||
email VARCHAR(255),
|
||||
phone VARCHAR(50),
|
||||
|
||||
-- Address
|
||||
address_street VARCHAR(500),
|
||||
address_city VARCHAR(100),
|
||||
address_state VARCHAR(100),
|
||||
address_zip VARCHAR(10),
|
||||
address_country VARCHAR(2) DEFAULT 'MX',
|
||||
|
||||
-- Branding
|
||||
logo_url VARCHAR(500),
|
||||
primary_color VARCHAR(7),
|
||||
|
||||
-- Owner and plan
|
||||
owner_id UUID NOT NULL,
|
||||
plan_id VARCHAR(50) NOT NULL REFERENCES plans(id),
|
||||
|
||||
-- Status
|
||||
status tenant_status NOT NULL DEFAULT 'pending',
|
||||
|
||||
-- Settings (JSON for flexibility)
|
||||
settings JSONB NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Metadata
|
||||
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
|
||||
);
|
||||
|
||||
-- Indexes for tenants
|
||||
CREATE INDEX idx_tenants_slug ON tenants(slug);
|
||||
CREATE INDEX idx_tenants_rfc ON tenants(rfc) WHERE rfc IS NOT NULL;
|
||||
CREATE INDEX idx_tenants_owner ON tenants(owner_id);
|
||||
CREATE INDEX idx_tenants_plan ON tenants(plan_id);
|
||||
CREATE INDEX idx_tenants_status ON tenants(status);
|
||||
CREATE INDEX idx_tenants_created ON tenants(created_at);
|
||||
|
||||
-- ============================================================================
|
||||
-- USERS TABLE
|
||||
-- Platform users (can belong to multiple tenants via user_tenants)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Authentication
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255),
|
||||
|
||||
-- Profile
|
||||
first_name VARCHAR(100),
|
||||
last_name VARCHAR(100),
|
||||
phone VARCHAR(50),
|
||||
avatar_url VARCHAR(500),
|
||||
|
||||
-- Default role (for super_admin only)
|
||||
default_role user_role NOT NULL DEFAULT 'viewer',
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
is_email_verified BOOLEAN NOT NULL DEFAULT false,
|
||||
email_verified_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Security
|
||||
failed_login_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
locked_until TIMESTAMP WITH TIME ZONE,
|
||||
password_changed_at TIMESTAMP WITH TIME ZONE,
|
||||
must_change_password BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Two-factor authentication
|
||||
two_factor_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
two_factor_secret VARCHAR(255),
|
||||
two_factor_recovery_codes TEXT[],
|
||||
|
||||
-- Preferences
|
||||
preferences JSONB NOT NULL DEFAULT '{}',
|
||||
timezone VARCHAR(50) DEFAULT 'America/Mexico_City',
|
||||
locale VARCHAR(10) DEFAULT 'es-MX',
|
||||
|
||||
-- Metadata
|
||||
last_login_at TIMESTAMP WITH TIME ZONE,
|
||||
last_login_ip INET,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for users
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
CREATE INDEX idx_users_active ON users(is_active);
|
||||
CREATE INDEX idx_users_default_role ON users(default_role) WHERE default_role = 'super_admin';
|
||||
CREATE INDEX idx_users_last_login ON users(last_login_at);
|
||||
|
||||
-- ============================================================================
|
||||
-- USER_TENANTS TABLE
|
||||
-- Association between users and tenants with role
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE user_tenants (
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Role within this tenant
|
||||
role user_role NOT NULL DEFAULT 'viewer',
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Invitation
|
||||
invited_by UUID REFERENCES users(id),
|
||||
invited_at TIMESTAMP WITH TIME ZONE,
|
||||
accepted_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
||||
PRIMARY KEY (user_id, tenant_id)
|
||||
);
|
||||
|
||||
-- Indexes for user_tenants
|
||||
CREATE INDEX idx_user_tenants_tenant ON user_tenants(tenant_id);
|
||||
CREATE INDEX idx_user_tenants_user ON user_tenants(user_id);
|
||||
CREATE INDEX idx_user_tenants_role ON user_tenants(role);
|
||||
|
||||
-- ============================================================================
|
||||
-- USER_SESSIONS TABLE
|
||||
-- Active user sessions for authentication
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE user_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Session token (hashed)
|
||||
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
refresh_token_hash VARCHAR(255),
|
||||
|
||||
-- Device info
|
||||
user_agent TEXT,
|
||||
ip_address INET,
|
||||
device_type VARCHAR(50),
|
||||
device_name VARCHAR(255),
|
||||
|
||||
-- Location (approximate)
|
||||
location_city VARCHAR(100),
|
||||
location_country VARCHAR(2),
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Timestamps
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
refresh_expires_at TIMESTAMP WITH TIME ZONE,
|
||||
last_activity_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
revoked_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Indexes for user_sessions
|
||||
CREATE INDEX idx_user_sessions_user ON user_sessions(user_id);
|
||||
CREATE INDEX idx_user_sessions_tenant ON user_sessions(tenant_id);
|
||||
CREATE INDEX idx_user_sessions_token ON user_sessions(token_hash);
|
||||
CREATE INDEX idx_user_sessions_expires ON user_sessions(expires_at) WHERE is_active = true;
|
||||
CREATE INDEX idx_user_sessions_active ON user_sessions(user_id, is_active) WHERE is_active = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- SUBSCRIPTIONS TABLE
|
||||
-- Tenant subscription management
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE subscriptions (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
plan_id VARCHAR(50) NOT NULL REFERENCES plans(id),
|
||||
|
||||
-- Status
|
||||
status subscription_status NOT NULL DEFAULT 'trial',
|
||||
|
||||
-- Billing cycle
|
||||
billing_cycle VARCHAR(20) NOT NULL DEFAULT 'monthly', -- monthly, yearly
|
||||
|
||||
-- Dates
|
||||
trial_ends_at TIMESTAMP WITH TIME ZONE,
|
||||
current_period_start TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
current_period_end TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
cancelled_at TIMESTAMP WITH TIME ZONE,
|
||||
cancel_at_period_end BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Payment info (Stripe or other processor)
|
||||
payment_processor VARCHAR(50),
|
||||
external_subscription_id VARCHAR(255),
|
||||
external_customer_id VARCHAR(255),
|
||||
|
||||
-- Pricing at subscription time
|
||||
price_cents INTEGER NOT NULL,
|
||||
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
|
||||
|
||||
-- Usage tracking
|
||||
usage_cfdis_current INTEGER NOT NULL DEFAULT 0,
|
||||
usage_storage_mb_current DECIMAL(10,2) NOT NULL DEFAULT 0,
|
||||
usage_api_calls_current INTEGER NOT NULL DEFAULT 0,
|
||||
usage_reset_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for subscriptions
|
||||
CREATE INDEX idx_subscriptions_tenant ON subscriptions(tenant_id);
|
||||
CREATE INDEX idx_subscriptions_plan ON subscriptions(plan_id);
|
||||
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
|
||||
CREATE INDEX idx_subscriptions_period_end ON subscriptions(current_period_end);
|
||||
CREATE INDEX idx_subscriptions_external ON subscriptions(external_subscription_id) WHERE external_subscription_id IS NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- AUDIT_LOG TABLE
|
||||
-- Comprehensive audit trail for compliance
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE audit_log (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Context
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE SET NULL,
|
||||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
|
||||
-- Action info
|
||||
action VARCHAR(100) NOT NULL,
|
||||
entity_type VARCHAR(100) NOT NULL,
|
||||
entity_id VARCHAR(255),
|
||||
|
||||
-- Change tracking
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
details JSONB,
|
||||
|
||||
-- Request context
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
request_id VARCHAR(100),
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for audit_log (optimized for queries)
|
||||
CREATE INDEX idx_audit_log_tenant ON audit_log(tenant_id, created_at DESC);
|
||||
CREATE INDEX idx_audit_log_user ON audit_log(user_id, created_at DESC);
|
||||
CREATE INDEX idx_audit_log_action ON audit_log(action, created_at DESC);
|
||||
CREATE INDEX idx_audit_log_entity ON audit_log(entity_type, entity_id, created_at DESC);
|
||||
CREATE INDEX idx_audit_log_created ON audit_log(created_at DESC);
|
||||
|
||||
-- Partition audit_log by month for better performance (optional, can be enabled later)
|
||||
-- This is a placeholder comment - actual partitioning would require more setup
|
||||
|
||||
-- ============================================================================
|
||||
-- BACKGROUND_JOBS TABLE
|
||||
-- Async job queue for long-running tasks
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE background_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Context
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
|
||||
-- Job info
|
||||
job_type VARCHAR(100) NOT NULL,
|
||||
job_name VARCHAR(255),
|
||||
queue VARCHAR(50) NOT NULL DEFAULT 'default',
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- Payload
|
||||
payload JSONB NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Status
|
||||
status job_status NOT NULL DEFAULT 'pending',
|
||||
progress INTEGER DEFAULT 0, -- 0-100
|
||||
|
||||
-- Results
|
||||
result JSONB,
|
||||
error_message TEXT,
|
||||
error_stack TEXT,
|
||||
|
||||
-- Retry logic
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
max_attempts INTEGER NOT NULL DEFAULT 3,
|
||||
|
||||
-- Scheduling
|
||||
scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
started_at TIMESTAMP WITH TIME ZONE,
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Timeout
|
||||
timeout_seconds INTEGER DEFAULT 3600, -- 1 hour default
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for background_jobs
|
||||
CREATE INDEX idx_background_jobs_tenant ON background_jobs(tenant_id);
|
||||
CREATE INDEX idx_background_jobs_status ON background_jobs(status, scheduled_at) WHERE status IN ('pending', 'running');
|
||||
CREATE INDEX idx_background_jobs_queue ON background_jobs(queue, priority DESC, scheduled_at) WHERE status = 'pending';
|
||||
CREATE INDEX idx_background_jobs_type ON background_jobs(job_type, status);
|
||||
CREATE INDEX idx_background_jobs_scheduled ON background_jobs(scheduled_at) WHERE status = 'pending';
|
||||
|
||||
-- ============================================================================
|
||||
-- API_KEYS TABLE
|
||||
-- API keys for external integrations
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE api_keys (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Key info
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- The key (only prefix stored for display, full hash for verification)
|
||||
key_prefix VARCHAR(10) NOT NULL, -- First 8 chars for identification
|
||||
key_hash VARCHAR(255) NOT NULL UNIQUE, -- SHA-256 hash of full key
|
||||
|
||||
-- Permissions (scopes)
|
||||
scopes TEXT[] NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Restrictions
|
||||
allowed_ips INET[],
|
||||
allowed_origins TEXT[],
|
||||
|
||||
-- Rate limiting
|
||||
rate_limit_per_minute INTEGER DEFAULT 60,
|
||||
rate_limit_per_day INTEGER DEFAULT 10000,
|
||||
|
||||
-- Usage tracking
|
||||
last_used_at TIMESTAMP WITH TIME ZONE,
|
||||
usage_count BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Expiration
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Metadata
|
||||
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
revoked_at TIMESTAMP WITH TIME ZONE,
|
||||
revoked_by UUID REFERENCES users(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Indexes for api_keys
|
||||
CREATE INDEX idx_api_keys_tenant ON api_keys(tenant_id);
|
||||
CREATE INDEX idx_api_keys_hash ON api_keys(key_hash) WHERE is_active = true;
|
||||
CREATE INDEX idx_api_keys_prefix ON api_keys(key_prefix);
|
||||
CREATE INDEX idx_api_keys_active ON api_keys(tenant_id, is_active) WHERE is_active = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- PASSWORD_RESET_TOKENS TABLE
|
||||
-- Tokens for password reset functionality
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE password_reset_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
used_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Index for password_reset_tokens
|
||||
CREATE INDEX idx_password_reset_tokens_user ON password_reset_tokens(user_id);
|
||||
CREATE INDEX idx_password_reset_tokens_token ON password_reset_tokens(token_hash) WHERE used_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- EMAIL_VERIFICATION_TOKENS TABLE
|
||||
-- Tokens for email verification
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE email_verification_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
verified_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Index for email_verification_tokens
|
||||
CREATE INDEX idx_email_verification_tokens_user ON email_verification_tokens(user_id);
|
||||
CREATE INDEX idx_email_verification_tokens_token ON email_verification_tokens(token_hash) WHERE verified_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- INVITATION_TOKENS TABLE
|
||||
-- Tokens for user invitations to tenants
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE invitation_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
role user_role NOT NULL DEFAULT 'viewer',
|
||||
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
invited_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
accepted_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Index for invitation_tokens
|
||||
CREATE INDEX idx_invitation_tokens_tenant ON invitation_tokens(tenant_id);
|
||||
CREATE INDEX idx_invitation_tokens_email ON invitation_tokens(email);
|
||||
CREATE INDEX idx_invitation_tokens_token ON invitation_tokens(token_hash) WHERE accepted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- SYSTEM_SETTINGS TABLE
|
||||
-- Global platform settings
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE system_settings (
|
||||
key VARCHAR(100) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
value_type VARCHAR(20) NOT NULL DEFAULT 'string', -- string, integer, boolean, json
|
||||
category VARCHAR(50) NOT NULL DEFAULT 'general',
|
||||
description TEXT,
|
||||
is_sensitive BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- NOTIFICATIONS TABLE
|
||||
-- System and user notifications
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE notifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Notification content
|
||||
type VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
message TEXT,
|
||||
data JSONB,
|
||||
|
||||
-- Action
|
||||
action_url VARCHAR(500),
|
||||
action_label VARCHAR(100),
|
||||
|
||||
-- Status
|
||||
is_read BOOLEAN NOT NULL DEFAULT false,
|
||||
read_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Delivery
|
||||
channels TEXT[] NOT NULL DEFAULT '{"in_app"}', -- in_app, email, push
|
||||
email_sent_at TIMESTAMP WITH TIME ZONE,
|
||||
push_sent_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Indexes for notifications
|
||||
CREATE INDEX idx_notifications_user ON notifications(user_id, created_at DESC);
|
||||
CREATE INDEX idx_notifications_unread ON notifications(user_id, is_read, created_at DESC) WHERE is_read = false;
|
||||
CREATE INDEX idx_notifications_tenant ON notifications(tenant_id, created_at DESC);
|
||||
|
||||
-- ============================================================================
|
||||
-- FUNCTIONS AND TRIGGERS
|
||||
-- ============================================================================
|
||||
|
||||
-- Function to update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Apply update_updated_at trigger to all relevant tables
|
||||
CREATE TRIGGER update_plans_updated_at BEFORE UPDATE ON plans
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_tenants_updated_at BEFORE UPDATE ON tenants
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_user_tenants_updated_at BEFORE UPDATE ON user_tenants
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON subscriptions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_background_jobs_updated_at BEFORE UPDATE ON background_jobs
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_api_keys_updated_at BEFORE UPDATE ON api_keys
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_system_settings_updated_at BEFORE UPDATE ON system_settings
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ============================================================================
|
||||
-- ROW LEVEL SECURITY (RLS) POLICIES
|
||||
-- Enable RLS for multi-tenant security
|
||||
-- ============================================================================
|
||||
|
||||
-- Note: RLS policies would be configured here in production
|
||||
-- For now, security is handled at the application layer with schema-based isolation
|
||||
|
||||
-- ============================================================================
|
||||
-- COMMENTS FOR DOCUMENTATION
|
||||
-- ============================================================================
|
||||
|
||||
COMMENT ON TABLE plans IS 'Subscription plans available in the platform';
|
||||
COMMENT ON TABLE tenants IS 'Companies/organizations using the platform (each gets their own schema)';
|
||||
COMMENT ON TABLE users IS 'Platform users (authentication and profile)';
|
||||
COMMENT ON TABLE user_tenants IS 'Many-to-many relationship between users and tenants with role';
|
||||
COMMENT ON TABLE user_sessions IS 'Active user sessions for JWT-based authentication';
|
||||
COMMENT ON TABLE subscriptions IS 'Tenant subscription and billing information';
|
||||
COMMENT ON TABLE audit_log IS 'Comprehensive audit trail for security and compliance';
|
||||
COMMENT ON TABLE background_jobs IS 'Async job queue for long-running tasks (SAT sync, reports, etc.)';
|
||||
COMMENT ON TABLE api_keys IS 'API keys for external integrations and third-party access';
|
||||
COMMENT ON TABLE notifications IS 'In-app and push notifications for users';
|
||||
889
packages/database/src/migrations/002_tenant_schema.sql
Normal file
889
packages/database/src/migrations/002_tenant_schema.sql
Normal file
@@ -0,0 +1,889 @@
|
||||
-- ============================================================================
|
||||
-- Horux Strategy - Tenant Schema Template
|
||||
-- Version: 002
|
||||
-- Description: Tables created for each tenant in their own schema
|
||||
-- Note: ${SCHEMA_NAME} will be replaced with the actual schema name
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- ENUM TYPES (tenant-specific)
|
||||
-- ============================================================================
|
||||
|
||||
-- Transaction type
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'transaction_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE transaction_type AS ENUM ('income', 'expense', 'transfer', 'adjustment');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Transaction status
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'transaction_status' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE transaction_status AS ENUM ('pending', 'confirmed', 'reconciled', 'voided');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- CFDI status
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cfdi_status' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE cfdi_status AS ENUM ('active', 'cancelled', 'pending_cancellation');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- CFDI type (comprobante)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cfdi_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE cfdi_type AS ENUM ('I', 'E', 'T', 'N', 'P'); -- Ingreso, Egreso, Traslado, Nomina, Pago
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Contact type
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'contact_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE contact_type AS ENUM ('customer', 'supplier', 'both', 'employee');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Category type
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'category_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE category_type AS ENUM ('income', 'expense', 'cost', 'other');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Account type
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'account_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE account_type AS ENUM ('asset', 'liability', 'equity', 'revenue', 'expense');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Alert severity
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'alert_severity' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE alert_severity AS ENUM ('info', 'warning', 'critical');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Report status
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'report_status' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) THEN
|
||||
CREATE TYPE report_status AS ENUM ('draft', 'generating', 'completed', 'failed', 'archived');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- ============================================================================
|
||||
-- SAT_CREDENTIALS TABLE
|
||||
-- Encrypted FIEL (e.firma) credentials for SAT integration
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE sat_credentials (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- RFC associated with credentials
|
||||
rfc VARCHAR(13) NOT NULL UNIQUE,
|
||||
|
||||
-- FIEL Components (encrypted with AES-256)
|
||||
-- The actual encryption key is stored securely in environment variables
|
||||
cer_file_encrypted BYTEA NOT NULL, -- .cer file content (encrypted)
|
||||
key_file_encrypted BYTEA NOT NULL, -- .key file content (encrypted)
|
||||
password_encrypted BYTEA NOT NULL, -- FIEL password (encrypted)
|
||||
|
||||
-- Certificate metadata
|
||||
cer_serial_number VARCHAR(50),
|
||||
cer_issued_at TIMESTAMP WITH TIME ZONE,
|
||||
cer_expires_at TIMESTAMP WITH TIME ZONE,
|
||||
cer_issuer VARCHAR(255),
|
||||
|
||||
-- CIEC credentials (optional, for portal access)
|
||||
ciec_password_encrypted BYTEA,
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
is_valid BOOLEAN NOT NULL DEFAULT false, -- Set after validation
|
||||
last_validated_at TIMESTAMP WITH TIME ZONE,
|
||||
validation_error TEXT,
|
||||
|
||||
-- SAT sync settings
|
||||
sync_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
sync_frequency_hours INTEGER DEFAULT 24,
|
||||
last_sync_at TIMESTAMP WITH TIME ZONE,
|
||||
last_sync_status VARCHAR(50),
|
||||
last_sync_error TEXT,
|
||||
|
||||
-- Encryption metadata
|
||||
encryption_version INTEGER NOT NULL DEFAULT 1,
|
||||
encryption_iv BYTEA, -- Initialization vector
|
||||
|
||||
-- Metadata
|
||||
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()
|
||||
);
|
||||
|
||||
-- Indexes for sat_credentials
|
||||
CREATE INDEX idx_sat_credentials_rfc ON sat_credentials(rfc);
|
||||
CREATE INDEX idx_sat_credentials_active ON sat_credentials(is_active, is_valid);
|
||||
CREATE INDEX idx_sat_credentials_sync ON sat_credentials(sync_enabled, last_sync_at) WHERE sync_enabled = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- CFDIS TABLE
|
||||
-- CFDI 4.0 compliant invoice storage
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE cfdis (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- CFDI Identifiers
|
||||
uuid_fiscal UUID NOT NULL UNIQUE, -- UUID from SAT (timbre fiscal)
|
||||
serie VARCHAR(25),
|
||||
folio VARCHAR(40),
|
||||
|
||||
-- Type and status
|
||||
tipo_comprobante cfdi_type NOT NULL, -- I, E, T, N, P
|
||||
status cfdi_status NOT NULL DEFAULT 'active',
|
||||
|
||||
-- Dates
|
||||
fecha_emision TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
fecha_timbrado TIMESTAMP WITH TIME ZONE,
|
||||
fecha_cancelacion TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Emisor (seller)
|
||||
emisor_rfc VARCHAR(13) NOT NULL,
|
||||
emisor_nombre VARCHAR(500) NOT NULL,
|
||||
emisor_regimen_fiscal VARCHAR(3) NOT NULL, -- SAT catalog code
|
||||
|
||||
-- Receptor (buyer)
|
||||
receptor_rfc VARCHAR(13) NOT NULL,
|
||||
receptor_nombre VARCHAR(500) NOT NULL,
|
||||
receptor_regimen_fiscal VARCHAR(3),
|
||||
receptor_domicilio_fiscal VARCHAR(5), -- CP
|
||||
receptor_uso_cfdi VARCHAR(4) NOT NULL, -- SAT catalog code
|
||||
|
||||
-- Amounts
|
||||
subtotal DECIMAL(18,2) NOT NULL,
|
||||
descuento DECIMAL(18,2) DEFAULT 0,
|
||||
total DECIMAL(18,2) NOT NULL,
|
||||
|
||||
-- Tax breakdown
|
||||
total_impuestos_trasladados DECIMAL(18,2) DEFAULT 0,
|
||||
total_impuestos_retenidos DECIMAL(18,2) DEFAULT 0,
|
||||
|
||||
-- IVA specific
|
||||
iva_16 DECIMAL(18,2) DEFAULT 0, -- IVA 16%
|
||||
iva_8 DECIMAL(18,2) DEFAULT 0, -- IVA 8% (frontera)
|
||||
iva_0 DECIMAL(18,2) DEFAULT 0, -- IVA 0%
|
||||
iva_exento DECIMAL(18,2) DEFAULT 0, -- IVA exento
|
||||
|
||||
-- ISR retention
|
||||
isr_retenido DECIMAL(18,2) DEFAULT 0,
|
||||
iva_retenido DECIMAL(18,2) DEFAULT 0,
|
||||
|
||||
-- Currency
|
||||
moneda VARCHAR(3) NOT NULL DEFAULT 'MXN',
|
||||
tipo_cambio DECIMAL(18,6) DEFAULT 1,
|
||||
|
||||
-- Payment info
|
||||
forma_pago VARCHAR(2), -- SAT catalog
|
||||
metodo_pago VARCHAR(3), -- PUE, PPD
|
||||
condiciones_pago VARCHAR(255),
|
||||
|
||||
-- Related documents
|
||||
cfdi_relacionados JSONB, -- Array of related CFDI UUIDs
|
||||
tipo_relacion VARCHAR(2), -- SAT catalog
|
||||
|
||||
-- Concepts/items (denormalized for quick access)
|
||||
conceptos JSONB NOT NULL, -- Array of line items
|
||||
|
||||
-- Full XML storage
|
||||
xml_content TEXT, -- Original XML
|
||||
xml_hash VARCHAR(64), -- SHA-256 of XML
|
||||
|
||||
-- Digital stamps
|
||||
sello_cfdi TEXT, -- Digital signature
|
||||
sello_sat TEXT, -- SAT signature
|
||||
certificado_sat VARCHAR(50),
|
||||
cadena_original_tfd TEXT,
|
||||
|
||||
-- Direction (for the tenant)
|
||||
is_emitted BOOLEAN NOT NULL, -- true = we issued it, false = we received it
|
||||
|
||||
-- Categorization
|
||||
category_id UUID,
|
||||
contact_id UUID,
|
||||
|
||||
-- Reconciliation
|
||||
is_reconciled BOOLEAN NOT NULL DEFAULT false,
|
||||
reconciled_at TIMESTAMP WITH TIME ZONE,
|
||||
reconciled_by UUID,
|
||||
|
||||
-- AI-generated insights
|
||||
ai_category_suggestion VARCHAR(100),
|
||||
ai_confidence_score DECIMAL(5,4),
|
||||
|
||||
-- Source
|
||||
source VARCHAR(50) NOT NULL DEFAULT 'sat_sync', -- sat_sync, manual, api
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for cfdis (optimized for reporting queries)
|
||||
CREATE INDEX idx_cfdis_uuid_fiscal ON cfdis(uuid_fiscal);
|
||||
CREATE INDEX idx_cfdis_fecha_emision ON cfdis(fecha_emision DESC);
|
||||
CREATE INDEX idx_cfdis_emisor_rfc ON cfdis(emisor_rfc);
|
||||
CREATE INDEX idx_cfdis_receptor_rfc ON cfdis(receptor_rfc);
|
||||
CREATE INDEX idx_cfdis_tipo ON cfdis(tipo_comprobante);
|
||||
CREATE INDEX idx_cfdis_status ON cfdis(status);
|
||||
CREATE INDEX idx_cfdis_is_emitted ON cfdis(is_emitted);
|
||||
CREATE INDEX idx_cfdis_category ON cfdis(category_id) WHERE category_id IS NOT NULL;
|
||||
CREATE INDEX idx_cfdis_contact ON cfdis(contact_id) WHERE contact_id IS NOT NULL;
|
||||
CREATE INDEX idx_cfdis_reconciled ON cfdis(is_reconciled, fecha_emision DESC) WHERE is_reconciled = false;
|
||||
|
||||
-- Composite indexes for common queries
|
||||
CREATE INDEX idx_cfdis_emitted_date ON cfdis(is_emitted, fecha_emision DESC);
|
||||
CREATE INDEX idx_cfdis_type_date ON cfdis(tipo_comprobante, fecha_emision DESC);
|
||||
CREATE INDEX idx_cfdis_month_report ON cfdis(DATE_TRUNC('month', fecha_emision), tipo_comprobante, is_emitted);
|
||||
|
||||
-- Full-text search index
|
||||
CREATE INDEX idx_cfdis_search ON cfdis USING gin(to_tsvector('spanish', emisor_nombre || ' ' || receptor_nombre || ' ' || COALESCE(serie, '') || ' ' || COALESCE(folio, '')));
|
||||
|
||||
-- ============================================================================
|
||||
-- TRANSACTIONS TABLE
|
||||
-- Unified financial transaction model
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE transactions (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Transaction info
|
||||
type transaction_type NOT NULL,
|
||||
status transaction_status NOT NULL DEFAULT 'pending',
|
||||
|
||||
-- Amount
|
||||
amount DECIMAL(18,2) NOT NULL,
|
||||
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
|
||||
exchange_rate DECIMAL(18,6) DEFAULT 1,
|
||||
amount_mxn DECIMAL(18,2) NOT NULL, -- Always in MXN for reporting
|
||||
|
||||
-- Dates
|
||||
transaction_date DATE NOT NULL,
|
||||
value_date DATE, -- Settlement date
|
||||
recorded_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Description
|
||||
description TEXT,
|
||||
reference VARCHAR(255),
|
||||
notes TEXT,
|
||||
|
||||
-- Categorization
|
||||
category_id UUID,
|
||||
account_id UUID,
|
||||
contact_id UUID,
|
||||
|
||||
-- Related documents
|
||||
cfdi_id UUID REFERENCES cfdis(id) ON DELETE SET NULL,
|
||||
|
||||
-- Bank reconciliation
|
||||
bank_transaction_id VARCHAR(255),
|
||||
bank_account_id VARCHAR(100),
|
||||
bank_description TEXT,
|
||||
|
||||
-- Recurring
|
||||
is_recurring BOOLEAN NOT NULL DEFAULT false,
|
||||
recurring_pattern JSONB, -- Frequency, end date, etc.
|
||||
parent_transaction_id UUID REFERENCES transactions(id) ON DELETE SET NULL,
|
||||
|
||||
-- Attachments
|
||||
attachments JSONB, -- Array of file references
|
||||
|
||||
-- Tags for custom classification
|
||||
tags TEXT[],
|
||||
|
||||
-- Reconciliation status
|
||||
is_reconciled BOOLEAN NOT NULL DEFAULT false,
|
||||
reconciled_at TIMESTAMP WITH TIME ZONE,
|
||||
reconciled_by UUID,
|
||||
|
||||
-- Approval workflow (for larger amounts)
|
||||
requires_approval BOOLEAN NOT NULL DEFAULT false,
|
||||
approved_at TIMESTAMP WITH TIME ZONE,
|
||||
approved_by UUID,
|
||||
|
||||
-- AI categorization
|
||||
ai_category_id UUID,
|
||||
ai_confidence DECIMAL(5,4),
|
||||
ai_notes TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
voided_at TIMESTAMP WITH TIME ZONE,
|
||||
voided_by UUID,
|
||||
void_reason TEXT
|
||||
);
|
||||
|
||||
-- Indexes for transactions
|
||||
CREATE INDEX idx_transactions_date ON transactions(transaction_date DESC);
|
||||
CREATE INDEX idx_transactions_type ON transactions(type);
|
||||
CREATE INDEX idx_transactions_status ON transactions(status);
|
||||
CREATE INDEX idx_transactions_category ON transactions(category_id);
|
||||
CREATE INDEX idx_transactions_account ON transactions(account_id);
|
||||
CREATE INDEX idx_transactions_contact ON transactions(contact_id);
|
||||
CREATE INDEX idx_transactions_cfdi ON transactions(cfdi_id) WHERE cfdi_id IS NOT NULL;
|
||||
CREATE INDEX idx_transactions_reconciled ON transactions(is_reconciled, transaction_date DESC) WHERE is_reconciled = false;
|
||||
|
||||
-- Composite indexes for reporting
|
||||
CREATE INDEX idx_transactions_monthly ON transactions(DATE_TRUNC('month', transaction_date), type, status);
|
||||
CREATE INDEX idx_transactions_category_date ON transactions(category_id, transaction_date DESC) WHERE category_id IS NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- CONTACTS TABLE
|
||||
-- Customers and suppliers
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE contacts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Type
|
||||
type contact_type NOT NULL,
|
||||
|
||||
-- Basic info
|
||||
name VARCHAR(500) NOT NULL,
|
||||
trade_name VARCHAR(500), -- Nombre comercial
|
||||
|
||||
-- Tax info (RFC)
|
||||
rfc VARCHAR(13),
|
||||
regimen_fiscal VARCHAR(3), -- SAT catalog
|
||||
uso_cfdi_default VARCHAR(4), -- Default uso CFDI
|
||||
|
||||
-- Contact info
|
||||
email VARCHAR(255),
|
||||
phone VARCHAR(50),
|
||||
mobile VARCHAR(50),
|
||||
website VARCHAR(255),
|
||||
|
||||
-- Address
|
||||
address_street VARCHAR(500),
|
||||
address_interior VARCHAR(50),
|
||||
address_exterior VARCHAR(50),
|
||||
address_neighborhood VARCHAR(200), -- Colonia
|
||||
address_city VARCHAR(100),
|
||||
address_municipality VARCHAR(100), -- Municipio/Delegacion
|
||||
address_state VARCHAR(100),
|
||||
address_zip VARCHAR(5),
|
||||
address_country VARCHAR(2) DEFAULT 'MX',
|
||||
|
||||
-- Bank info
|
||||
bank_name VARCHAR(100),
|
||||
bank_account VARCHAR(20),
|
||||
bank_clabe VARCHAR(18),
|
||||
|
||||
-- Credit terms
|
||||
credit_days INTEGER DEFAULT 0,
|
||||
credit_limit DECIMAL(18,2) DEFAULT 0,
|
||||
|
||||
-- Balances (denormalized for performance)
|
||||
balance_receivable DECIMAL(18,2) DEFAULT 0,
|
||||
balance_payable DECIMAL(18,2) DEFAULT 0,
|
||||
|
||||
-- Classification
|
||||
category VARCHAR(100),
|
||||
tags TEXT[],
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Notes
|
||||
notes TEXT,
|
||||
|
||||
-- Metadata
|
||||
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 contacts
|
||||
CREATE INDEX idx_contacts_type ON contacts(type);
|
||||
CREATE INDEX idx_contacts_rfc ON contacts(rfc) WHERE rfc IS NOT NULL;
|
||||
CREATE INDEX idx_contacts_name ON contacts(name);
|
||||
CREATE INDEX idx_contacts_active ON contacts(is_active);
|
||||
CREATE INDEX idx_contacts_search ON contacts USING gin(to_tsvector('spanish', name || ' ' || COALESCE(trade_name, '') || ' ' || COALESCE(rfc, '')));
|
||||
|
||||
-- ============================================================================
|
||||
-- CATEGORIES TABLE
|
||||
-- Transaction/expense categories
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE categories (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Identification
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Type
|
||||
type category_type NOT NULL,
|
||||
|
||||
-- Hierarchy
|
||||
parent_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
||||
level INTEGER NOT NULL DEFAULT 0,
|
||||
path TEXT, -- Materialized path for hierarchy
|
||||
|
||||
-- SAT mapping
|
||||
sat_key VARCHAR(10), -- Clave producto/servicio SAT
|
||||
|
||||
-- Budget
|
||||
budget_monthly DECIMAL(18,2),
|
||||
budget_yearly DECIMAL(18,2),
|
||||
|
||||
-- Display
|
||||
color VARCHAR(7), -- Hex color
|
||||
icon VARCHAR(50),
|
||||
display_order INTEGER DEFAULT 0,
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false, -- Prevent deletion of system categories
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for categories
|
||||
CREATE INDEX idx_categories_code ON categories(code);
|
||||
CREATE INDEX idx_categories_type ON categories(type);
|
||||
CREATE INDEX idx_categories_parent ON categories(parent_id);
|
||||
CREATE INDEX idx_categories_active ON categories(is_active);
|
||||
|
||||
-- ============================================================================
|
||||
-- ACCOUNTS TABLE
|
||||
-- Chart of accounts (catalogo de cuentas)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE accounts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Identification
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Type
|
||||
type account_type NOT NULL,
|
||||
|
||||
-- Hierarchy
|
||||
parent_id UUID REFERENCES accounts(id) ON DELETE SET NULL,
|
||||
level INTEGER NOT NULL DEFAULT 0,
|
||||
path TEXT, -- Materialized path
|
||||
|
||||
-- SAT mapping (for Contabilidad Electronica)
|
||||
sat_code VARCHAR(20), -- Codigo agrupador SAT
|
||||
sat_nature VARCHAR(1), -- D = Deudora, A = Acreedora
|
||||
|
||||
-- Balances (denormalized)
|
||||
balance_debit DECIMAL(18,2) DEFAULT 0,
|
||||
balance_credit DECIMAL(18,2) DEFAULT 0,
|
||||
balance_current DECIMAL(18,2) DEFAULT 0,
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
allows_movements BOOLEAN NOT NULL DEFAULT true, -- Can have direct transactions
|
||||
|
||||
-- Display
|
||||
display_order INTEGER DEFAULT 0,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for accounts
|
||||
CREATE INDEX idx_accounts_code ON accounts(code);
|
||||
CREATE INDEX idx_accounts_type ON accounts(type);
|
||||
CREATE INDEX idx_accounts_parent ON accounts(parent_id);
|
||||
CREATE INDEX idx_accounts_active ON accounts(is_active);
|
||||
|
||||
-- ============================================================================
|
||||
-- METRICS_CACHE TABLE
|
||||
-- Pre-computed metrics for dashboard performance
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE metrics_cache (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Metric identification
|
||||
metric_key VARCHAR(100) NOT NULL,
|
||||
period_type VARCHAR(20) NOT NULL, -- daily, weekly, monthly, yearly
|
||||
period_start DATE NOT NULL,
|
||||
period_end DATE NOT NULL,
|
||||
|
||||
-- Dimension (optional filtering)
|
||||
dimension_type VARCHAR(50), -- category, contact, account, etc.
|
||||
dimension_id UUID,
|
||||
|
||||
-- Values
|
||||
value_numeric DECIMAL(18,4),
|
||||
value_json JSONB,
|
||||
|
||||
-- Comparison
|
||||
previous_value DECIMAL(18,4),
|
||||
change_percent DECIMAL(8,4),
|
||||
change_absolute DECIMAL(18,4),
|
||||
|
||||
-- Validity
|
||||
computed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
valid_until TIMESTAMP WITH TIME ZONE,
|
||||
is_stale BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Unique constraint on metric + period + dimension
|
||||
UNIQUE(metric_key, period_type, period_start, dimension_type, dimension_id)
|
||||
);
|
||||
|
||||
-- Indexes for metrics_cache
|
||||
CREATE INDEX idx_metrics_cache_key ON metrics_cache(metric_key);
|
||||
CREATE INDEX idx_metrics_cache_period ON metrics_cache(period_type, period_start DESC);
|
||||
CREATE INDEX idx_metrics_cache_dimension ON metrics_cache(dimension_type, dimension_id) WHERE dimension_type IS NOT NULL;
|
||||
CREATE INDEX idx_metrics_cache_stale ON metrics_cache(is_stale) WHERE is_stale = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- ALERTS TABLE
|
||||
-- Financial alerts and notifications
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE alerts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Alert info
|
||||
type VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
severity alert_severity NOT NULL DEFAULT 'info',
|
||||
|
||||
-- Related entity
|
||||
entity_type VARCHAR(50),
|
||||
entity_id UUID,
|
||||
|
||||
-- Threshold that triggered the alert
|
||||
threshold_type VARCHAR(50),
|
||||
threshold_value DECIMAL(18,4),
|
||||
current_value DECIMAL(18,4),
|
||||
|
||||
-- Actions
|
||||
action_url VARCHAR(500),
|
||||
action_label VARCHAR(100),
|
||||
action_data JSONB,
|
||||
|
||||
-- Status
|
||||
is_read BOOLEAN NOT NULL DEFAULT false,
|
||||
is_dismissed BOOLEAN NOT NULL DEFAULT false,
|
||||
read_at TIMESTAMP WITH TIME ZONE,
|
||||
dismissed_at TIMESTAMP WITH TIME ZONE,
|
||||
dismissed_by UUID,
|
||||
|
||||
-- Recurrence
|
||||
is_recurring BOOLEAN NOT NULL DEFAULT false,
|
||||
last_triggered_at TIMESTAMP WITH TIME ZONE,
|
||||
trigger_count INTEGER DEFAULT 1,
|
||||
|
||||
-- Auto-resolve
|
||||
auto_resolved BOOLEAN NOT NULL DEFAULT false,
|
||||
resolved_at TIMESTAMP WITH TIME ZONE,
|
||||
resolved_by UUID,
|
||||
resolution_notes TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Indexes for alerts
|
||||
CREATE INDEX idx_alerts_type ON alerts(type);
|
||||
CREATE INDEX idx_alerts_severity ON alerts(severity);
|
||||
CREATE INDEX idx_alerts_unread ON alerts(is_read, created_at DESC) WHERE is_read = false;
|
||||
CREATE INDEX idx_alerts_entity ON alerts(entity_type, entity_id) WHERE entity_type IS NOT NULL;
|
||||
CREATE INDEX idx_alerts_active ON alerts(is_dismissed, created_at DESC) WHERE is_dismissed = false;
|
||||
|
||||
-- ============================================================================
|
||||
-- REPORTS TABLE
|
||||
-- Generated reports
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE reports (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Report info
|
||||
type VARCHAR(100) NOT NULL, -- balance_general, estado_resultados, flujo_efectivo, etc.
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Period
|
||||
period_start DATE NOT NULL,
|
||||
period_end DATE NOT NULL,
|
||||
comparison_period_start DATE,
|
||||
comparison_period_end DATE,
|
||||
|
||||
-- Status
|
||||
status report_status NOT NULL DEFAULT 'draft',
|
||||
|
||||
-- Parameters used to generate
|
||||
parameters JSONB,
|
||||
|
||||
-- Output
|
||||
data JSONB, -- Report data
|
||||
file_url VARCHAR(500), -- PDF/Excel URL
|
||||
file_format VARCHAR(10), -- pdf, xlsx, csv
|
||||
|
||||
-- Scheduling
|
||||
is_scheduled BOOLEAN NOT NULL DEFAULT false,
|
||||
schedule_cron VARCHAR(50),
|
||||
next_scheduled_at TIMESTAMP WITH TIME ZONE,
|
||||
last_generated_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Sharing
|
||||
is_shared BOOLEAN NOT NULL DEFAULT false,
|
||||
shared_with UUID[],
|
||||
share_token VARCHAR(100),
|
||||
share_expires_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Metadata
|
||||
generated_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for reports
|
||||
CREATE INDEX idx_reports_type ON reports(type);
|
||||
CREATE INDEX idx_reports_status ON reports(status);
|
||||
CREATE INDEX idx_reports_period ON reports(period_start, period_end);
|
||||
CREATE INDEX idx_reports_scheduled ON reports(is_scheduled, next_scheduled_at) WHERE is_scheduled = true;
|
||||
|
||||
-- ============================================================================
|
||||
-- SETTINGS TABLE
|
||||
-- Tenant-specific settings
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE settings (
|
||||
key VARCHAR(100) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
value_type VARCHAR(20) NOT NULL DEFAULT 'string', -- string, integer, boolean, json
|
||||
category VARCHAR(50) NOT NULL DEFAULT 'general',
|
||||
label VARCHAR(200),
|
||||
description TEXT,
|
||||
is_sensitive BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- BANK_ACCOUNTS TABLE
|
||||
-- Connected bank accounts
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE bank_accounts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Bank info
|
||||
bank_name VARCHAR(100) NOT NULL,
|
||||
bank_code VARCHAR(10), -- SPEI bank code
|
||||
|
||||
-- Account info
|
||||
account_number VARCHAR(20),
|
||||
clabe VARCHAR(18),
|
||||
account_type VARCHAR(50), -- checking, savings, credit
|
||||
|
||||
-- Display
|
||||
alias VARCHAR(100),
|
||||
currency VARCHAR(3) DEFAULT 'MXN',
|
||||
|
||||
-- Balance (cached from bank sync)
|
||||
balance_available DECIMAL(18,2),
|
||||
balance_current DECIMAL(18,2),
|
||||
balance_updated_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Connection
|
||||
connection_provider VARCHAR(50), -- belvo, finerio, manual
|
||||
connection_id VARCHAR(255),
|
||||
connection_status VARCHAR(50),
|
||||
last_sync_at TIMESTAMP WITH TIME ZONE,
|
||||
last_sync_error TEXT,
|
||||
|
||||
-- Categorization
|
||||
account_id UUID REFERENCES accounts(id), -- Link to chart of accounts
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for bank_accounts
|
||||
CREATE INDEX idx_bank_accounts_active ON bank_accounts(is_active);
|
||||
CREATE INDEX idx_bank_accounts_connection ON bank_accounts(connection_provider, connection_id) WHERE connection_id IS NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- BUDGET_ITEMS TABLE
|
||||
-- Budget planning
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE budget_items (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Period
|
||||
year INTEGER NOT NULL,
|
||||
month INTEGER NOT NULL, -- 1-12, or 0 for yearly
|
||||
|
||||
-- Category
|
||||
category_id UUID REFERENCES categories(id) ON DELETE CASCADE,
|
||||
account_id UUID REFERENCES accounts(id) ON DELETE CASCADE,
|
||||
|
||||
-- Amounts
|
||||
amount_budgeted DECIMAL(18,2) NOT NULL,
|
||||
amount_actual DECIMAL(18,2) DEFAULT 0,
|
||||
amount_variance DECIMAL(18,2) GENERATED ALWAYS AS (amount_actual - amount_budgeted) STORED,
|
||||
|
||||
-- Notes
|
||||
notes TEXT,
|
||||
|
||||
-- Status
|
||||
is_locked BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
-- Metadata
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- One budget per category/account per period
|
||||
UNIQUE(year, month, category_id),
|
||||
UNIQUE(year, month, account_id)
|
||||
);
|
||||
|
||||
-- Indexes for budget_items
|
||||
CREATE INDEX idx_budget_items_period ON budget_items(year, month);
|
||||
CREATE INDEX idx_budget_items_category ON budget_items(category_id);
|
||||
CREATE INDEX idx_budget_items_account ON budget_items(account_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- ATTACHMENTS TABLE
|
||||
-- File attachments for various entities
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE attachments (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Related entity
|
||||
entity_type VARCHAR(50) NOT NULL, -- cfdi, transaction, contact, etc.
|
||||
entity_id UUID NOT NULL,
|
||||
|
||||
-- File info
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_type VARCHAR(100),
|
||||
file_size INTEGER,
|
||||
file_url VARCHAR(500) NOT NULL,
|
||||
|
||||
-- Storage
|
||||
storage_provider VARCHAR(50) DEFAULT 'local', -- local, s3, gcs
|
||||
storage_path VARCHAR(500),
|
||||
|
||||
-- Metadata
|
||||
uploaded_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for attachments
|
||||
CREATE INDEX idx_attachments_entity ON attachments(entity_type, entity_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- TRIGGERS FOR TENANT SCHEMA
|
||||
-- ============================================================================
|
||||
|
||||
-- Update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Apply triggers
|
||||
CREATE TRIGGER update_sat_credentials_updated_at BEFORE UPDATE ON sat_credentials
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_cfdis_updated_at BEFORE UPDATE ON cfdis
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_transactions_updated_at BEFORE UPDATE ON transactions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_contacts_updated_at BEFORE UPDATE ON contacts
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_categories_updated_at BEFORE UPDATE ON categories
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_accounts_updated_at BEFORE UPDATE ON accounts
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_reports_updated_at BEFORE UPDATE ON reports
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_settings_updated_at BEFORE UPDATE ON settings
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_bank_accounts_updated_at BEFORE UPDATE ON bank_accounts
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_budget_items_updated_at BEFORE UPDATE ON budget_items
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ============================================================================
|
||||
-- FOREIGN KEY CONSTRAINTS
|
||||
-- ============================================================================
|
||||
|
||||
-- Add foreign keys after all tables are created
|
||||
ALTER TABLE cfdis ADD CONSTRAINT fk_cfdis_category
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE cfdis ADD CONSTRAINT fk_cfdis_contact
|
||||
FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE transactions ADD CONSTRAINT fk_transactions_category
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE transactions ADD CONSTRAINT fk_transactions_account
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE transactions ADD CONSTRAINT fk_transactions_contact
|
||||
FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE SET NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- COMMENTS
|
||||
-- ============================================================================
|
||||
|
||||
COMMENT ON TABLE sat_credentials IS 'Encrypted SAT FIEL credentials for each tenant';
|
||||
COMMENT ON TABLE cfdis IS 'CFDI 4.0 invoices (emitted and received)';
|
||||
COMMENT ON TABLE transactions IS 'Unified financial transactions';
|
||||
COMMENT ON TABLE contacts IS 'Customers and suppliers';
|
||||
COMMENT ON TABLE categories IS 'Transaction categorization';
|
||||
COMMENT ON TABLE accounts IS 'Chart of accounts (catalogo de cuentas)';
|
||||
COMMENT ON TABLE metrics_cache IS 'Pre-computed metrics for dashboard';
|
||||
COMMENT ON TABLE alerts IS 'Financial alerts and notifications';
|
||||
COMMENT ON TABLE reports IS 'Generated financial reports';
|
||||
COMMENT ON TABLE settings IS 'Tenant-specific configuration';
|
||||
Reference in New Issue
Block a user