erp-core-database-v2/ddl/00-prerequisites.sql

208 lines
7.1 KiB
PL/PgSQL

-- ============================================================================
-- ERP GENERIC - DATABASE PREREQUISITES
-- ============================================================================
-- Version: 1.0.0
-- Description: Extensions, common types, and utility functions
-- Execute: FIRST (before any schema)
-- ============================================================================
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- Password hashing
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- Trigram similarity (fuzzy search)
CREATE EXTENSION IF NOT EXISTS "unaccent"; -- Remove accents for search
-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================
-- Function: Update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION update_updated_at_column() IS
'Generic trigger function to auto-update updated_at timestamp on row modification';
-- Function: Normalize text for search (remove accents, lowercase)
CREATE OR REPLACE FUNCTION normalize_search_text(p_text TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN LOWER(unaccent(COALESCE(p_text, '')));
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION normalize_search_text(TEXT) IS
'Normalize text for search by removing accents and converting to lowercase';
-- Function: Generate random alphanumeric code
CREATE OR REPLACE FUNCTION generate_random_code(p_length INTEGER DEFAULT 8)
RETURNS TEXT AS $$
DECLARE
chars TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
result TEXT := '';
i INTEGER;
BEGIN
FOR i IN 1..p_length LOOP
result := result || substr(chars, floor(random() * length(chars) + 1)::INTEGER, 1);
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION generate_random_code(INTEGER) IS
'Generate random alphanumeric code of specified length (default 8)';
-- Function: Validate email format
CREATE OR REPLACE FUNCTION is_valid_email(p_email TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN p_email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION is_valid_email(TEXT) IS
'Validate email format using regex';
-- Function: Validate phone number format (basic)
CREATE OR REPLACE FUNCTION is_valid_phone(p_phone TEXT)
RETURNS BOOLEAN AS $$
BEGIN
-- Basic validation: only digits, spaces, dashes, parentheses, plus sign
RETURN p_phone ~ '^[\d\s\-\(\)\+]+$' AND length(regexp_replace(p_phone, '[^\d]', '', 'g')) >= 7;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION is_valid_phone(TEXT) IS
'Validate phone number format (at least 7 digits)';
-- Function: Clean phone number (keep only digits)
CREATE OR REPLACE FUNCTION clean_phone(p_phone TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN regexp_replace(COALESCE(p_phone, ''), '[^\d]', '', 'g');
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION clean_phone(TEXT) IS
'Remove non-numeric characters from phone number';
-- Function: Calculate age from date
CREATE OR REPLACE FUNCTION calculate_age(p_birthdate DATE)
RETURNS INTEGER AS $$
BEGIN
IF p_birthdate IS NULL THEN
RETURN NULL;
END IF;
RETURN EXTRACT(YEAR FROM age(CURRENT_DATE, p_birthdate))::INTEGER;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION calculate_age(DATE) IS
'Calculate age in years from birthdate';
-- Function: Get current fiscal year start
CREATE OR REPLACE FUNCTION get_fiscal_year_start(p_date DATE DEFAULT CURRENT_DATE)
RETURNS DATE AS $$
BEGIN
-- Assuming fiscal year starts January 1st
-- Modify if different fiscal year start is needed
RETURN DATE_TRUNC('year', p_date)::DATE;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION get_fiscal_year_start(DATE) IS
'Get the start date of fiscal year for a given date (default: January 1st)';
-- Function: Round to decimal places
CREATE OR REPLACE FUNCTION round_currency(p_amount NUMERIC, p_decimals INTEGER DEFAULT 2)
RETURNS NUMERIC AS $$
BEGIN
RETURN ROUND(COALESCE(p_amount, 0), p_decimals);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
COMMENT ON FUNCTION round_currency(NUMERIC, INTEGER) IS
'Round numeric value to specified decimal places (default 2 for currency)';
-- ============================================================================
-- COMMON TYPES
-- ============================================================================
-- Type: Money with currency (for multi-currency support)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'money_amount') THEN
CREATE TYPE money_amount AS (
amount NUMERIC(15, 2),
currency_code CHAR(3)
);
END IF;
END $$;
COMMENT ON TYPE money_amount IS
'Composite type for storing monetary values with currency code';
-- Type: Address components
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'address_components') THEN
CREATE TYPE address_components AS (
street VARCHAR(255),
street2 VARCHAR(255),
city VARCHAR(100),
state VARCHAR(100),
zip VARCHAR(20),
country_code CHAR(2)
);
END IF;
END $$;
COMMENT ON TYPE address_components IS
'Composite type for address components (street, city, state, zip, country)';
-- ============================================================================
-- SCHEMA CREATION
-- ============================================================================
-- Create all schemas upfront to avoid circular dependency issues
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS core;
CREATE SCHEMA IF NOT EXISTS analytics;
CREATE SCHEMA IF NOT EXISTS financial;
CREATE SCHEMA IF NOT EXISTS inventory;
CREATE SCHEMA IF NOT EXISTS purchase;
CREATE SCHEMA IF NOT EXISTS sales;
CREATE SCHEMA IF NOT EXISTS projects;
CREATE SCHEMA IF NOT EXISTS system;
-- Set search path to include all schemas
ALTER DATABASE erp_generic SET search_path TO public, auth, core, analytics, financial, inventory, purchase, sales, projects, system;
-- Grant usage on schemas to public role (will be refined per-user later)
GRANT USAGE ON SCHEMA auth TO PUBLIC;
GRANT USAGE ON SCHEMA core TO PUBLIC;
GRANT USAGE ON SCHEMA analytics TO PUBLIC;
GRANT USAGE ON SCHEMA financial TO PUBLIC;
GRANT USAGE ON SCHEMA inventory TO PUBLIC;
GRANT USAGE ON SCHEMA purchase TO PUBLIC;
GRANT USAGE ON SCHEMA sales TO PUBLIC;
GRANT USAGE ON SCHEMA projects TO PUBLIC;
GRANT USAGE ON SCHEMA system TO PUBLIC;
-- ============================================================================
-- PREREQUISITES COMPLETE
-- ============================================================================
DO $$
BEGIN
RAISE NOTICE 'Prerequisites installed successfully!';
RAISE NOTICE 'Extensions: uuid-ossp, pgcrypto, pg_trgm, unaccent';
RAISE NOTICE 'Schemas created: auth, core, analytics, financial, inventory, purchase, sales, projects, system';
RAISE NOTICE 'Utility functions: 9 functions installed';
END $$;