T2.1: Renamed warehouse_type -> construction_warehouse_type
in almacenes_proyecto to avoid naming conflict with ERP-Core
T2.3: Added user_id column to hr.employees
- Optional FK to auth.users (employee may not have system access)
- Unique constraint for 1:1 mapping
- Conditional FK (only created if auth.users exists)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
175 lines
7.0 KiB
SQL
175 lines
7.0 KiB
SQL
-- ============================================================================
|
|
-- HR Schema DDL - Extension de RRHH para Construccion
|
|
-- Modulo: MAI-007 RRHH y Asistencias
|
|
-- Version: 1.0.0
|
|
-- Fecha: 2025-12-06
|
|
-- ============================================================================
|
|
-- POLITICA: CARGA LIMPIA (ver DIRECTIVA-POLITICA-CARGA-LIMPIA.md)
|
|
-- Este archivo es parte de la fuente de verdad DDL.
|
|
-- ============================================================================
|
|
|
|
-- Verificar prerequisitos
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'auth') THEN
|
|
RAISE EXCEPTION 'Schema auth no existe. ERP-Core debe estar instalado';
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'tenants') THEN
|
|
RAISE EXCEPTION 'Tabla auth.tenants no existe. ERP-Core debe estar instalado';
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'construction') THEN
|
|
RAISE EXCEPTION 'Schema construction no existe. Ejecutar primero 01-construction-schema-ddl.sql';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Crear schema si no existe
|
|
CREATE SCHEMA IF NOT EXISTS hr;
|
|
|
|
-- Configurar search_path
|
|
SET search_path TO hr, construction, core, core_shared, public;
|
|
|
|
-- ============================================================================
|
|
-- TABLAS BASE (requeridas por HSE y otros modulos)
|
|
-- ============================================================================
|
|
|
|
-- Tabla: Empleados
|
|
CREATE TABLE IF NOT EXISTS hr.employees (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
codigo VARCHAR(20) NOT NULL,
|
|
nombre VARCHAR(100) NOT NULL,
|
|
apellido_paterno VARCHAR(100) NOT NULL,
|
|
apellido_materno VARCHAR(100),
|
|
curp VARCHAR(18),
|
|
rfc VARCHAR(13),
|
|
nss VARCHAR(11),
|
|
fecha_nacimiento DATE,
|
|
genero VARCHAR(1),
|
|
email VARCHAR(255),
|
|
telefono VARCHAR(20),
|
|
direccion TEXT,
|
|
fecha_ingreso DATE NOT NULL,
|
|
fecha_baja DATE,
|
|
puesto_id UUID,
|
|
departamento VARCHAR(100),
|
|
tipo_contrato VARCHAR(50),
|
|
salario_diario DECIMAL(10,2),
|
|
estado VARCHAR(20) NOT NULL DEFAULT 'activo',
|
|
foto_url VARCHAR(500),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
created_by UUID REFERENCES auth.users(id),
|
|
-- Mapeo a usuario del sistema (opcional - empleado puede no tener acceso)
|
|
user_id UUID,
|
|
|
|
CONSTRAINT uq_employees_codigo UNIQUE (tenant_id, codigo),
|
|
CONSTRAINT uq_employees_curp UNIQUE (tenant_id, curp),
|
|
CONSTRAINT uq_employees_user_id UNIQUE (user_id)
|
|
);
|
|
|
|
-- Tabla: Puestos
|
|
CREATE TABLE IF NOT EXISTS hr.puestos (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
codigo VARCHAR(20) NOT NULL,
|
|
nombre VARCHAR(100) NOT NULL,
|
|
descripcion TEXT,
|
|
nivel_riesgo VARCHAR(20),
|
|
requiere_capacitacion_especial BOOLEAN DEFAULT false,
|
|
activo BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT uq_puestos_codigo UNIQUE (tenant_id, codigo)
|
|
);
|
|
|
|
-- Agregar FK de puesto a empleados
|
|
ALTER TABLE hr.employees
|
|
ADD CONSTRAINT fk_employees_puesto
|
|
FOREIGN KEY (puesto_id) REFERENCES hr.puestos(id);
|
|
|
|
-- FK condicional: employees.user_id → auth.users (si tabla existe)
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users') THEN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_employees_user') THEN
|
|
ALTER TABLE hr.employees
|
|
ADD CONSTRAINT fk_employees_user
|
|
FOREIGN KEY (user_id) REFERENCES auth.users(id)
|
|
ON DELETE SET NULL;
|
|
RAISE NOTICE 'FK creada: hr.employees.user_id → auth.users';
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Tabla: Asignacion de empleados a obras
|
|
CREATE TABLE IF NOT EXISTS hr.employee_fraccionamientos (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
|
|
fecha_inicio DATE NOT NULL,
|
|
fecha_fin DATE,
|
|
rol VARCHAR(50),
|
|
activo BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT uq_employee_fraccionamiento UNIQUE (employee_id, fraccionamiento_id, fecha_inicio)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- INDICES
|
|
-- ============================================================================
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_employees_tenant ON hr.employees(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_employees_estado ON hr.employees(estado);
|
|
CREATE INDEX IF NOT EXISTS idx_employees_puesto ON hr.employees(puesto_id);
|
|
CREATE INDEX IF NOT EXISTS idx_employees_user_id ON hr.employees(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_puestos_tenant ON hr.puestos(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_employee_fraccionamientos_employee ON hr.employee_fraccionamientos(employee_id);
|
|
CREATE INDEX IF NOT EXISTS idx_employee_fraccionamientos_fraccionamiento ON hr.employee_fraccionamientos(fraccionamiento_id);
|
|
|
|
-- ============================================================================
|
|
-- ROW LEVEL SECURITY
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE hr.employees ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.puestos ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.employee_fraccionamientos ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_employees ON hr.employees
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
CREATE POLICY tenant_isolation_puestos ON hr.puestos
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
CREATE POLICY tenant_isolation_employee_fraccionamientos ON hr.employee_fraccionamientos
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- ============================================================================
|
|
-- TRIGGERS
|
|
-- ============================================================================
|
|
|
|
CREATE TRIGGER trg_employees_updated_at
|
|
BEFORE UPDATE ON hr.employees
|
|
FOR EACH ROW EXECUTE FUNCTION core_shared.set_updated_at();
|
|
|
|
CREATE TRIGGER trg_puestos_updated_at
|
|
BEFORE UPDATE ON hr.puestos
|
|
FOR EACH ROW EXECUTE FUNCTION core_shared.set_updated_at();
|
|
|
|
-- ============================================================================
|
|
-- COMENTARIOS
|
|
-- ============================================================================
|
|
|
|
COMMENT ON TABLE hr.employees IS 'Empleados de la empresa';
|
|
COMMENT ON TABLE hr.puestos IS 'Catalogo de puestos de trabajo';
|
|
COMMENT ON TABLE hr.employee_fraccionamientos IS 'Asignacion de empleados a obras/fraccionamientos';
|
|
|
|
-- ============================================================================
|
|
-- FIN
|
|
-- ============================================================================
|