395 lines
15 KiB
PL/PgSQL
395 lines
15 KiB
PL/PgSQL
-- =====================================================
|
|
-- SCHEMA: hr (EXTENSION)
|
|
-- PROPOSITO: Extensiones de RRHH para construccion
|
|
-- MODULOS: MAI-007 (RRHH y Asistencias)
|
|
-- FECHA: 2025-11-24
|
|
-- TIPO: Extension del ERP Generico (MGN-010)
|
|
-- =====================================================
|
|
|
|
-- NOTA: Este archivo contiene SOLO las extensiones especificas
|
|
-- de construccion. Las tablas base estan en el ERP Generico.
|
|
|
|
-- =====================================================
|
|
-- TYPES (ENUMs) ADICIONALES
|
|
-- =====================================================
|
|
|
|
CREATE TYPE hr.attendance_type_construction AS ENUM (
|
|
'manual', -- Captura manual
|
|
'biometric', -- Biometrico
|
|
'gps', -- GPS desde app movil
|
|
'qr_code' -- Codigo QR
|
|
);
|
|
|
|
CREATE TYPE hr.worker_type AS ENUM (
|
|
'permanent', -- Planta
|
|
'temporary', -- Temporal
|
|
'subcontract', -- Subcontratado
|
|
'piece_rate' -- Destajista
|
|
);
|
|
|
|
CREATE TYPE hr.destajo_status AS ENUM (
|
|
'pending', -- Pendiente medicion
|
|
'measured', -- Medido
|
|
'approved', -- Aprobado
|
|
'paid' -- Pagado
|
|
);
|
|
|
|
-- =====================================================
|
|
-- TABLES - EXTENSIONES CONSTRUCCION
|
|
-- =====================================================
|
|
|
|
-- Tabla: employee_construction (extension de empleados)
|
|
-- Extiende: hr.employees
|
|
CREATE TABLE hr.employee_construction (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL, -- FK a hr.employees (ERP Generico)
|
|
worker_type hr.worker_type NOT NULL DEFAULT 'permanent',
|
|
subcontratista_id UUID REFERENCES construction.subcontratistas(id),
|
|
|
|
-- Seguro Social
|
|
nss VARCHAR(15),
|
|
imss_registration_date DATE,
|
|
salary_type VARCHAR(20), -- fijo, mixto, variable
|
|
|
|
-- Capacitacion
|
|
stps_dc3_number VARCHAR(50), -- Constancia DC-3
|
|
stps_dc3_expiry DATE,
|
|
safety_training_date DATE,
|
|
first_aid_training_date DATE,
|
|
|
|
-- Documentos
|
|
has_medical_exam BOOLEAN DEFAULT FALSE,
|
|
medical_exam_date DATE,
|
|
has_ine BOOLEAN DEFAULT FALSE,
|
|
has_curp BOOLEAN DEFAULT FALSE,
|
|
has_rfc BOOLEAN DEFAULT FALSE,
|
|
|
|
-- Asignacion
|
|
default_fraccionamiento_id UUID REFERENCES construction.fraccionamientos(id),
|
|
specialty VARCHAR(100), -- Especialidad: alba~nil, electricista, plomero
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
deleted_at TIMESTAMP,
|
|
deleted_by UUID REFERENCES auth.users(id),
|
|
|
|
CONSTRAINT uq_employee_construction_employee_id UNIQUE (employee_id)
|
|
);
|
|
|
|
-- Tabla: asistencias (registro de asistencia)
|
|
CREATE TABLE hr.asistencias (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL, -- FK a hr.employees (ERP Generico)
|
|
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
|
|
attendance_date DATE NOT NULL,
|
|
attendance_type hr.attendance_type_construction NOT NULL DEFAULT 'manual',
|
|
|
|
-- Horarios
|
|
check_in_time TIMESTAMP,
|
|
check_out_time TIMESTAMP,
|
|
worked_hours DECIMAL(4,2) GENERATED ALWAYS AS (
|
|
CASE
|
|
WHEN check_out_time IS NOT NULL AND check_in_time IS NOT NULL
|
|
THEN EXTRACT(EPOCH FROM (check_out_time - check_in_time)) / 3600
|
|
ELSE NULL
|
|
END
|
|
) STORED,
|
|
overtime_hours DECIMAL(4,2) DEFAULT 0,
|
|
|
|
-- GPS
|
|
check_in_location GEOMETRY(POINT, 4326),
|
|
check_out_location GEOMETRY(POINT, 4326),
|
|
check_in_accuracy DECIMAL(8,2), -- Precision en metros
|
|
check_out_accuracy DECIMAL(8,2),
|
|
|
|
-- Validacion
|
|
is_valid BOOLEAN DEFAULT TRUE,
|
|
validation_notes TEXT,
|
|
validated_by UUID REFERENCES auth.users(id),
|
|
validated_at TIMESTAMP,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
deleted_at TIMESTAMP,
|
|
deleted_by UUID REFERENCES auth.users(id),
|
|
|
|
CONSTRAINT uq_asistencias_employee_date UNIQUE (employee_id, attendance_date)
|
|
);
|
|
|
|
-- Tabla: asistencia_biometrico (registros biometricos)
|
|
CREATE TABLE hr.asistencia_biometrico (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
asistencia_id UUID NOT NULL REFERENCES hr.asistencias(id) ON DELETE CASCADE,
|
|
device_id VARCHAR(50), -- ID del dispositivo biometrico
|
|
device_name VARCHAR(100),
|
|
biometric_type VARCHAR(20), -- fingerprint, face, iris
|
|
verification_score DECIMAL(5,2), -- Score de verificacion 0-100
|
|
raw_data TEXT, -- Datos crudos del dispositivo
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id)
|
|
);
|
|
|
|
-- Tabla: geocercas (geocercas para validacion GPS)
|
|
CREATE TABLE hr.geocercas (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
polygon GEOMETRY(POLYGON, 4326) NOT NULL,
|
|
radius_meters DECIMAL(8,2), -- Radio de tolerancia
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
deleted_at TIMESTAMP,
|
|
deleted_by UUID REFERENCES auth.users(id)
|
|
);
|
|
|
|
-- Tabla: destajo (trabajo a destajo)
|
|
CREATE TABLE hr.destajo (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL, -- FK a hr.employees
|
|
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
|
|
lote_id UUID REFERENCES construction.lotes(id),
|
|
departamento_id UUID REFERENCES construction.departamentos(id),
|
|
concepto_id UUID NOT NULL REFERENCES construction.conceptos(id),
|
|
period_start DATE NOT NULL,
|
|
period_end DATE NOT NULL,
|
|
status hr.destajo_status NOT NULL DEFAULT 'pending',
|
|
|
|
-- Cantidades
|
|
quantity_measured DECIMAL(12,4) DEFAULT 0,
|
|
unit_price DECIMAL(12,4) NOT NULL,
|
|
total_amount DECIMAL(14,2) GENERATED ALWAYS AS (quantity_measured * unit_price) STORED,
|
|
|
|
-- Medicion
|
|
measured_by UUID REFERENCES auth.users(id),
|
|
measured_at TIMESTAMP,
|
|
|
|
-- Aprobacion
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
approved_at TIMESTAMP,
|
|
|
|
-- Pago
|
|
payroll_id UUID, -- FK a nomina
|
|
paid_at TIMESTAMP,
|
|
|
|
notes TEXT,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
deleted_at TIMESTAMP,
|
|
deleted_by UUID REFERENCES auth.users(id)
|
|
);
|
|
|
|
-- Tabla: destajo_detalle (detalle de mediciones)
|
|
CREATE TABLE hr.destajo_detalle (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
destajo_id UUID NOT NULL REFERENCES hr.destajo(id) ON DELETE CASCADE,
|
|
description VARCHAR(255) NOT NULL,
|
|
quantity DECIMAL(12,4) NOT NULL,
|
|
formula TEXT, -- Formula de calculo
|
|
photo_url VARCHAR(500),
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id)
|
|
);
|
|
|
|
-- Tabla: cuadrilla (cuadrillas de trabajo)
|
|
CREATE TABLE hr.cuadrillas (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
|
|
code VARCHAR(20) NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
leader_id UUID, -- FK a hr.employees (lider de cuadrilla)
|
|
specialty VARCHAR(100),
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
deleted_at TIMESTAMP,
|
|
deleted_by UUID REFERENCES auth.users(id),
|
|
|
|
CONSTRAINT uq_cuadrillas_code_fracc UNIQUE (fraccionamiento_id, code)
|
|
);
|
|
|
|
-- Tabla: cuadrilla_miembros (miembros de cuadrilla)
|
|
CREATE TABLE hr.cuadrilla_miembros (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
cuadrilla_id UUID NOT NULL REFERENCES hr.cuadrillas(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL, -- FK a hr.employees
|
|
role VARCHAR(50), -- lider, oficial, ayudante
|
|
start_date DATE NOT NULL,
|
|
end_date DATE,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP,
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
|
|
CONSTRAINT uq_cuadrilla_employee UNIQUE (cuadrilla_id, employee_id, start_date)
|
|
);
|
|
|
|
-- =====================================================
|
|
-- INDICES
|
|
-- =====================================================
|
|
|
|
-- Employee Construction
|
|
CREATE INDEX idx_employee_construction_tenant_id ON hr.employee_construction(tenant_id);
|
|
CREATE INDEX idx_employee_construction_employee_id ON hr.employee_construction(employee_id);
|
|
CREATE INDEX idx_employee_construction_worker_type ON hr.employee_construction(worker_type);
|
|
CREATE INDEX idx_employee_construction_subcontratista_id ON hr.employee_construction(subcontratista_id);
|
|
|
|
-- Asistencias
|
|
CREATE INDEX idx_asistencias_tenant_id ON hr.asistencias(tenant_id);
|
|
CREATE INDEX idx_asistencias_employee_id ON hr.asistencias(employee_id);
|
|
CREATE INDEX idx_asistencias_fraccionamiento_id ON hr.asistencias(fraccionamiento_id);
|
|
CREATE INDEX idx_asistencias_date ON hr.asistencias(attendance_date);
|
|
CREATE INDEX idx_asistencias_type ON hr.asistencias(attendance_type);
|
|
|
|
-- Asistencia Biometrico
|
|
CREATE INDEX idx_asist_biometrico_tenant_id ON hr.asistencia_biometrico(tenant_id);
|
|
CREATE INDEX idx_asist_biometrico_asistencia_id ON hr.asistencia_biometrico(asistencia_id);
|
|
|
|
-- Geocercas
|
|
CREATE INDEX idx_geocercas_tenant_id ON hr.geocercas(tenant_id);
|
|
CREATE INDEX idx_geocercas_fraccionamiento_id ON hr.geocercas(fraccionamiento_id);
|
|
CREATE INDEX idx_geocercas_polygon ON hr.geocercas USING GIST(polygon);
|
|
|
|
-- Destajo
|
|
CREATE INDEX idx_destajo_tenant_id ON hr.destajo(tenant_id);
|
|
CREATE INDEX idx_destajo_employee_id ON hr.destajo(employee_id);
|
|
CREATE INDEX idx_destajo_fraccionamiento_id ON hr.destajo(fraccionamiento_id);
|
|
CREATE INDEX idx_destajo_lote_id ON hr.destajo(lote_id);
|
|
CREATE INDEX idx_destajo_concepto_id ON hr.destajo(concepto_id);
|
|
CREATE INDEX idx_destajo_status ON hr.destajo(status);
|
|
CREATE INDEX idx_destajo_period ON hr.destajo(period_start, period_end);
|
|
|
|
-- Destajo Detalle
|
|
CREATE INDEX idx_destajo_detalle_tenant_id ON hr.destajo_detalle(tenant_id);
|
|
CREATE INDEX idx_destajo_detalle_destajo_id ON hr.destajo_detalle(destajo_id);
|
|
|
|
-- Cuadrillas
|
|
CREATE INDEX idx_cuadrillas_tenant_id ON hr.cuadrillas(tenant_id);
|
|
CREATE INDEX idx_cuadrillas_fraccionamiento_id ON hr.cuadrillas(fraccionamiento_id);
|
|
CREATE INDEX idx_cuadrillas_leader_id ON hr.cuadrillas(leader_id);
|
|
|
|
-- Cuadrilla Miembros
|
|
CREATE INDEX idx_cuadrilla_miembros_tenant_id ON hr.cuadrilla_miembros(tenant_id);
|
|
CREATE INDEX idx_cuadrilla_miembros_cuadrilla_id ON hr.cuadrilla_miembros(cuadrilla_id);
|
|
CREATE INDEX idx_cuadrilla_miembros_employee_id ON hr.cuadrilla_miembros(employee_id);
|
|
|
|
-- =====================================================
|
|
-- ROW LEVEL SECURITY (RLS)
|
|
-- =====================================================
|
|
|
|
ALTER TABLE hr.employee_construction ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.asistencias ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.asistencia_biometrico ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.geocercas ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.destajo ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.destajo_detalle ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.cuadrillas ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.cuadrilla_miembros ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_employee_construction ON hr.employee_construction
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_asistencias ON hr.asistencias
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_asist_biometrico ON hr.asistencia_biometrico
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_geocercas ON hr.geocercas
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_destajo ON hr.destajo
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_destajo_detalle ON hr.destajo_detalle
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_cuadrillas ON hr.cuadrillas
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
CREATE POLICY tenant_isolation_cuadrilla_miembros ON hr.cuadrilla_miembros
|
|
USING (tenant_id = get_current_tenant_id());
|
|
|
|
-- =====================================================
|
|
-- FUNCTIONS
|
|
-- =====================================================
|
|
|
|
-- Funcion: validar asistencia dentro de geocerca
|
|
CREATE OR REPLACE FUNCTION hr.validate_attendance_location(
|
|
p_location GEOMETRY,
|
|
p_fraccionamiento_id UUID
|
|
)
|
|
RETURNS BOOLEAN AS $$
|
|
DECLARE
|
|
v_is_valid BOOLEAN;
|
|
BEGIN
|
|
SELECT EXISTS (
|
|
SELECT 1
|
|
FROM hr.geocercas g
|
|
WHERE g.fraccionamiento_id = p_fraccionamiento_id
|
|
AND g.is_active = TRUE
|
|
AND g.deleted_at IS NULL
|
|
AND (
|
|
ST_Contains(g.polygon, p_location) OR
|
|
ST_DWithin(g.polygon::geography, p_location::geography, COALESCE(g.radius_meters, 50))
|
|
)
|
|
) INTO v_is_valid;
|
|
|
|
RETURN v_is_valid;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
COMMENT ON FUNCTION hr.validate_attendance_location IS 'Valida si una ubicacion esta dentro de las geocercas del fraccionamiento';
|
|
|
|
-- =====================================================
|
|
-- COMENTARIOS
|
|
-- =====================================================
|
|
|
|
COMMENT ON TABLE hr.employee_construction IS 'Extension: datos adicionales de empleados para construccion';
|
|
COMMENT ON TABLE hr.asistencias IS 'Extension: registro de asistencia con GPS/biometrico';
|
|
COMMENT ON TABLE hr.asistencia_biometrico IS 'Extension: detalles de registro biometrico';
|
|
COMMENT ON TABLE hr.geocercas IS 'Extension: geocercas para validacion de ubicacion';
|
|
COMMENT ON TABLE hr.destajo IS 'Extension: trabajo a destajo/precio unitario';
|
|
COMMENT ON TABLE hr.destajo_detalle IS 'Extension: detalle de mediciones de destajo';
|
|
COMMENT ON TABLE hr.cuadrillas IS 'Extension: cuadrillas de trabajo';
|
|
COMMENT ON TABLE hr.cuadrilla_miembros IS 'Extension: miembros de cuadrillas';
|
|
|
|
-- =====================================================
|
|
-- FIN DE EXTENSIONES HR
|
|
-- =====================================================
|