-- ============================================================================ -- 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 $$;