🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
871 lines
29 KiB
PL/PgSQL
871 lines
29 KiB
PL/PgSQL
-- =====================================================
|
|
-- SCHEMA: hr
|
|
-- PROPOSITO: Human Resources Management
|
|
-- MODULOS: MGN-HR (Recursos Humanos)
|
|
-- FECHA: 2025-11-24
|
|
-- =====================================================
|
|
|
|
-- Crear schema
|
|
CREATE SCHEMA IF NOT EXISTS hr;
|
|
|
|
-- =====================================================
|
|
-- TYPES (ENUMs)
|
|
-- =====================================================
|
|
|
|
CREATE TYPE hr.contract_status AS ENUM (
|
|
'draft',
|
|
'active',
|
|
'expired',
|
|
'terminated',
|
|
'cancelled'
|
|
);
|
|
|
|
CREATE TYPE hr.contract_type AS ENUM (
|
|
'permanent',
|
|
'temporary',
|
|
'contractor',
|
|
'internship',
|
|
'part_time'
|
|
);
|
|
|
|
CREATE TYPE hr.leave_status AS ENUM (
|
|
'draft',
|
|
'submitted',
|
|
'approved',
|
|
'rejected',
|
|
'cancelled'
|
|
);
|
|
|
|
CREATE TYPE hr.leave_type AS ENUM (
|
|
'vacation',
|
|
'sick',
|
|
'personal',
|
|
'maternity',
|
|
'paternity',
|
|
'bereavement',
|
|
'unpaid',
|
|
'other'
|
|
);
|
|
|
|
CREATE TYPE hr.employee_status AS ENUM (
|
|
'active',
|
|
'inactive',
|
|
'on_leave',
|
|
'terminated'
|
|
);
|
|
|
|
-- =====================================================
|
|
-- TABLES
|
|
-- =====================================================
|
|
|
|
-- Tabla: departments (Departamentos)
|
|
CREATE TABLE hr.departments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(20),
|
|
parent_id UUID REFERENCES hr.departments(id),
|
|
manager_id UUID, -- References employees, set after table creation
|
|
|
|
description TEXT,
|
|
color VARCHAR(20),
|
|
|
|
active BOOLEAN DEFAULT TRUE,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, company_id, name)
|
|
);
|
|
|
|
-- Tabla: job_positions (Puestos de trabajo)
|
|
CREATE TABLE hr.job_positions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(100) NOT NULL,
|
|
department_id UUID REFERENCES hr.departments(id),
|
|
|
|
description TEXT,
|
|
requirements TEXT,
|
|
responsibilities TEXT,
|
|
|
|
min_salary DECIMAL(15, 2),
|
|
max_salary DECIMAL(15, 2),
|
|
|
|
active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, name)
|
|
);
|
|
|
|
-- Tabla: employees (Empleados)
|
|
CREATE TABLE hr.employees (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
employee_number VARCHAR(50) NOT NULL,
|
|
first_name VARCHAR(100) NOT NULL,
|
|
last_name VARCHAR(100) NOT NULL,
|
|
middle_name VARCHAR(100),
|
|
|
|
-- Usuario vinculado (opcional)
|
|
user_id UUID REFERENCES auth.users(id),
|
|
|
|
-- Informacion personal
|
|
birth_date DATE,
|
|
gender VARCHAR(20),
|
|
marital_status VARCHAR(20),
|
|
nationality VARCHAR(100),
|
|
identification_id VARCHAR(50),
|
|
identification_type VARCHAR(50),
|
|
social_security_number VARCHAR(50),
|
|
tax_id VARCHAR(50),
|
|
|
|
-- Contacto
|
|
email VARCHAR(255),
|
|
work_email VARCHAR(255),
|
|
phone VARCHAR(50),
|
|
work_phone VARCHAR(50),
|
|
mobile VARCHAR(50),
|
|
emergency_contact VARCHAR(255),
|
|
emergency_phone VARCHAR(50),
|
|
|
|
-- Direccion
|
|
street VARCHAR(255),
|
|
city VARCHAR(100),
|
|
state VARCHAR(100),
|
|
zip VARCHAR(20),
|
|
country VARCHAR(100),
|
|
|
|
-- Trabajo
|
|
department_id UUID REFERENCES hr.departments(id),
|
|
job_position_id UUID REFERENCES hr.job_positions(id),
|
|
manager_id UUID REFERENCES hr.employees(id),
|
|
|
|
hire_date DATE NOT NULL,
|
|
termination_date DATE,
|
|
status hr.employee_status NOT NULL DEFAULT 'active',
|
|
|
|
-- Datos bancarios
|
|
bank_name VARCHAR(100),
|
|
bank_account VARCHAR(50),
|
|
bank_clabe VARCHAR(20),
|
|
|
|
-- Foto
|
|
photo_url VARCHAR(500),
|
|
|
|
-- Notas
|
|
notes TEXT,
|
|
|
|
-- Auditoria
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, employee_number)
|
|
);
|
|
|
|
-- Add manager_id reference to departments
|
|
ALTER TABLE hr.departments ADD CONSTRAINT fk_departments_manager
|
|
FOREIGN KEY (manager_id) REFERENCES hr.employees(id);
|
|
|
|
-- Tabla: contracts (Contratos laborales)
|
|
CREATE TABLE hr.contracts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
name VARCHAR(100) NOT NULL,
|
|
reference VARCHAR(100),
|
|
|
|
-- Tipo y estado
|
|
contract_type hr.contract_type NOT NULL,
|
|
status hr.contract_status NOT NULL DEFAULT 'draft',
|
|
|
|
-- Puesto
|
|
job_position_id UUID REFERENCES hr.job_positions(id),
|
|
department_id UUID REFERENCES hr.departments(id),
|
|
|
|
-- Vigencia
|
|
date_start DATE NOT NULL,
|
|
date_end DATE,
|
|
trial_date_end DATE,
|
|
|
|
-- Compensacion
|
|
wage DECIMAL(15, 2) NOT NULL,
|
|
wage_type VARCHAR(20) DEFAULT 'monthly', -- hourly, daily, weekly, monthly, yearly
|
|
currency_id UUID REFERENCES core.currencies(id),
|
|
|
|
-- Horas
|
|
resource_calendar_id UUID, -- For future scheduling module
|
|
hours_per_week DECIMAL(5, 2) DEFAULT 40,
|
|
|
|
-- Beneficios y deducciones
|
|
vacation_days INTEGER DEFAULT 6,
|
|
christmas_bonus_days INTEGER DEFAULT 15,
|
|
|
|
-- Documentos
|
|
document_url VARCHAR(500),
|
|
|
|
-- Notas
|
|
notes TEXT,
|
|
|
|
-- Auditoria
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Tabla: leave_types (Tipos de ausencia configurables)
|
|
CREATE TABLE hr.leave_types (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(20),
|
|
leave_type hr.leave_type NOT NULL,
|
|
|
|
requires_approval BOOLEAN DEFAULT TRUE,
|
|
max_days INTEGER,
|
|
is_paid BOOLEAN DEFAULT TRUE,
|
|
|
|
color VARCHAR(20),
|
|
|
|
active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, name)
|
|
);
|
|
|
|
-- Tabla: leaves (Ausencias/Permisos)
|
|
CREATE TABLE hr.leaves (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
leave_type_id UUID NOT NULL REFERENCES hr.leave_types(id),
|
|
|
|
-- Solicitud
|
|
name VARCHAR(255),
|
|
date_from DATE NOT NULL,
|
|
date_to DATE NOT NULL,
|
|
number_of_days DECIMAL(5, 2) NOT NULL,
|
|
|
|
-- Estado
|
|
status hr.leave_status NOT NULL DEFAULT 'draft',
|
|
|
|
-- Descripcion
|
|
description TEXT,
|
|
|
|
-- Aprobacion
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
approved_at TIMESTAMP WITH TIME ZONE,
|
|
rejection_reason TEXT,
|
|
|
|
-- Auditoria
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- =====================================================
|
|
-- INDEXES
|
|
-- =====================================================
|
|
|
|
CREATE INDEX idx_departments_tenant ON hr.departments(tenant_id);
|
|
CREATE INDEX idx_departments_company ON hr.departments(company_id);
|
|
CREATE INDEX idx_departments_parent ON hr.departments(parent_id);
|
|
|
|
CREATE INDEX idx_job_positions_tenant ON hr.job_positions(tenant_id);
|
|
CREATE INDEX idx_job_positions_department ON hr.job_positions(department_id);
|
|
|
|
CREATE INDEX idx_employees_tenant ON hr.employees(tenant_id);
|
|
CREATE INDEX idx_employees_company ON hr.employees(company_id);
|
|
CREATE INDEX idx_employees_department ON hr.employees(department_id);
|
|
CREATE INDEX idx_employees_manager ON hr.employees(manager_id);
|
|
CREATE INDEX idx_employees_user ON hr.employees(user_id);
|
|
CREATE INDEX idx_employees_status ON hr.employees(status);
|
|
CREATE INDEX idx_employees_number ON hr.employees(employee_number);
|
|
|
|
CREATE INDEX idx_contracts_tenant ON hr.contracts(tenant_id);
|
|
CREATE INDEX idx_contracts_employee ON hr.contracts(employee_id);
|
|
CREATE INDEX idx_contracts_status ON hr.contracts(status);
|
|
CREATE INDEX idx_contracts_dates ON hr.contracts(date_start, date_end);
|
|
|
|
CREATE INDEX idx_leave_types_tenant ON hr.leave_types(tenant_id);
|
|
|
|
CREATE INDEX idx_leaves_tenant ON hr.leaves(tenant_id);
|
|
CREATE INDEX idx_leaves_employee ON hr.leaves(employee_id);
|
|
CREATE INDEX idx_leaves_status ON hr.leaves(status);
|
|
CREATE INDEX idx_leaves_dates ON hr.leaves(date_from, date_to);
|
|
|
|
-- =====================================================
|
|
-- TRIGGERS
|
|
-- =====================================================
|
|
|
|
CREATE TRIGGER update_departments_timestamp
|
|
BEFORE UPDATE ON hr.departments
|
|
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
|
|
|
|
CREATE TRIGGER update_job_positions_timestamp
|
|
BEFORE UPDATE ON hr.job_positions
|
|
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
|
|
|
|
CREATE TRIGGER update_employees_timestamp
|
|
BEFORE UPDATE ON hr.employees
|
|
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
|
|
|
|
CREATE TRIGGER update_contracts_timestamp
|
|
BEFORE UPDATE ON hr.contracts
|
|
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
|
|
|
|
CREATE TRIGGER update_leaves_timestamp
|
|
BEFORE UPDATE ON hr.leaves
|
|
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
|
|
|
|
-- =====================================================
|
|
-- ROW LEVEL SECURITY
|
|
-- =====================================================
|
|
|
|
-- Habilitar RLS
|
|
ALTER TABLE hr.departments ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.job_positions ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.employees ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.contracts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.leave_types ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.leaves ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Políticas de aislamiento por tenant
|
|
CREATE POLICY tenant_isolation_departments ON hr.departments
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
CREATE POLICY tenant_isolation_job_positions ON hr.job_positions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
CREATE POLICY tenant_isolation_employees ON hr.employees
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
CREATE POLICY tenant_isolation_contracts ON hr.contracts
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
CREATE POLICY tenant_isolation_leave_types ON hr.leave_types
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
CREATE POLICY tenant_isolation_leaves ON hr.leaves
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
-- =====================================================
|
|
-- COMMENTS
|
|
-- =====================================================
|
|
|
|
COMMENT ON TABLE hr.departments IS 'Departamentos de la organizacion';
|
|
COMMENT ON TABLE hr.job_positions IS 'Puestos de trabajo/posiciones';
|
|
COMMENT ON TABLE hr.employees IS 'Empleados de la organizacion';
|
|
COMMENT ON TABLE hr.contracts IS 'Contratos laborales';
|
|
COMMENT ON TABLE hr.leave_types IS 'Tipos de ausencia configurables';
|
|
COMMENT ON TABLE hr.leaves IS 'Solicitudes de ausencias/permisos';
|
|
|
|
-- =====================================================
|
|
-- COR-026: Employee Attendances
|
|
-- Equivalente a hr.attendance de Odoo
|
|
-- =====================================================
|
|
|
|
CREATE TABLE hr.attendances (
|
|
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 REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
check_in TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
check_out TIMESTAMP WITH TIME ZONE,
|
|
worked_hours DECIMAL(10,4),
|
|
overtime_hours DECIMAL(10,4) DEFAULT 0,
|
|
is_overtime BOOLEAN DEFAULT FALSE,
|
|
notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT valid_checkout CHECK (check_out IS NULL OR check_out > check_in)
|
|
);
|
|
|
|
CREATE INDEX idx_attendances_tenant ON hr.attendances(tenant_id);
|
|
CREATE INDEX idx_attendances_employee ON hr.attendances(employee_id);
|
|
CREATE INDEX idx_attendances_checkin ON hr.attendances(check_in);
|
|
CREATE INDEX idx_attendances_date ON hr.attendances(tenant_id, DATE(check_in));
|
|
|
|
-- Trigger para calcular worked_hours automaticamente
|
|
CREATE OR REPLACE FUNCTION hr.calculate_worked_hours()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.check_out IS NOT NULL THEN
|
|
NEW.worked_hours := EXTRACT(EPOCH FROM (NEW.check_out - NEW.check_in)) / 3600.0;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_attendances_calculate_hours
|
|
BEFORE INSERT OR UPDATE ON hr.attendances
|
|
FOR EACH ROW EXECUTE FUNCTION hr.calculate_worked_hours();
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.attendances ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_attendances ON hr.attendances
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
COMMENT ON TABLE hr.attendances IS 'COR-026: Employee attendances - Equivalent to hr.attendance';
|
|
|
|
-- =====================================================
|
|
-- COR-027: Leave Allocations
|
|
-- Equivalente a hr.leave.allocation de Odoo
|
|
-- =====================================================
|
|
|
|
CREATE TABLE hr.leave_allocations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
leave_type_id UUID NOT NULL REFERENCES hr.leave_types(id),
|
|
name VARCHAR(255),
|
|
number_of_days DECIMAL(10,2) NOT NULL,
|
|
date_from DATE,
|
|
date_to DATE,
|
|
status hr.leave_status DEFAULT 'draft',
|
|
allocation_type VARCHAR(20) DEFAULT 'regular', -- regular, accrual
|
|
notes TEXT,
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
approved_at TIMESTAMP WITH TIME ZONE,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_leave_allocations_tenant ON hr.leave_allocations(tenant_id);
|
|
CREATE INDEX idx_leave_allocations_employee ON hr.leave_allocations(employee_id);
|
|
CREATE INDEX idx_leave_allocations_type ON hr.leave_allocations(leave_type_id);
|
|
CREATE INDEX idx_leave_allocations_status ON hr.leave_allocations(status);
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.leave_allocations ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_leave_allocations ON hr.leave_allocations
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
COMMENT ON TABLE hr.leave_allocations IS 'COR-027: Leave allocations - Equivalent to hr.leave.allocation';
|
|
|
|
-- =====================================================
|
|
-- COR-061: Employee Additional Fields
|
|
-- Campos adicionales para alinear con Odoo hr.employee
|
|
-- =====================================================
|
|
|
|
ALTER TABLE hr.employees
|
|
ADD COLUMN IF NOT EXISTS work_location_id UUID, -- FK to work_locations
|
|
ADD COLUMN IF NOT EXISTS resource_id UUID, -- FK future resource.resource
|
|
ADD COLUMN IF NOT EXISTS resource_calendar_id UUID, -- FK future resource.calendar
|
|
ADD COLUMN IF NOT EXISTS company_country_id UUID REFERENCES core.countries(id),
|
|
ADD COLUMN IF NOT EXISTS private_street VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS private_city VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS private_state_id UUID, -- FK to core.states
|
|
ADD COLUMN IF NOT EXISTS private_zip VARCHAR(20),
|
|
ADD COLUMN IF NOT EXISTS private_country_id UUID REFERENCES core.countries(id),
|
|
ADD COLUMN IF NOT EXISTS private_phone VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS private_email VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS km_home_work INTEGER DEFAULT 0, -- Distancia casa-trabajo
|
|
ADD COLUMN IF NOT EXISTS children INTEGER DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS vehicle VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS vehicle_license_plate VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS visa_no VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS visa_expire DATE,
|
|
ADD COLUMN IF NOT EXISTS work_permit_no VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS work_permit_expiration_date DATE,
|
|
ADD COLUMN IF NOT EXISTS certificate VARCHAR(50), -- Nivel educativo
|
|
ADD COLUMN IF NOT EXISTS study_field VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS study_school VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS badge_id VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS pin VARCHAR(20), -- PIN para kiosk
|
|
ADD COLUMN IF NOT EXISTS barcode VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS color INTEGER DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS additional_note TEXT;
|
|
|
|
-- =====================================================
|
|
-- COR-062: Work Locations
|
|
-- Ubicaciones de trabajo (oficina, remoto, etc.)
|
|
-- =====================================================
|
|
|
|
CREATE TABLE hr.work_locations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(100) NOT NULL,
|
|
location_type VARCHAR(50) DEFAULT 'office', -- office, home, other
|
|
address_id UUID REFERENCES core.partners(id),
|
|
|
|
-- Control
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, name)
|
|
);
|
|
|
|
CREATE INDEX idx_work_locations_tenant ON hr.work_locations(tenant_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.work_locations ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_work_locations ON hr.work_locations
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
-- Agregar FK a employees
|
|
ALTER TABLE hr.employees ADD CONSTRAINT fk_employees_work_location
|
|
FOREIGN KEY (work_location_id) REFERENCES hr.work_locations(id);
|
|
|
|
COMMENT ON TABLE hr.work_locations IS 'COR-062: Work locations for employees';
|
|
|
|
-- =====================================================
|
|
-- COR-063: Employee Skills
|
|
-- Sistema de habilidades de empleados
|
|
-- =====================================================
|
|
|
|
CREATE TABLE hr.skill_types (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
skill_levels VARCHAR(50) DEFAULT 'basic', -- basic, intermediate, advanced, expert
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(tenant_id, name)
|
|
);
|
|
|
|
CREATE TABLE hr.skills (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
skill_type_id UUID NOT NULL REFERENCES hr.skill_types(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(tenant_id, skill_type_id, name)
|
|
);
|
|
|
|
CREATE TABLE hr.skill_levels (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
skill_type_id UUID NOT NULL REFERENCES hr.skill_types(id) ON DELETE CASCADE,
|
|
name VARCHAR(50) NOT NULL,
|
|
level INTEGER NOT NULL DEFAULT 1, -- 1-5 typically
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(tenant_id, skill_type_id, name)
|
|
);
|
|
|
|
CREATE TABLE hr.employee_skills (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
skill_id UUID NOT NULL REFERENCES hr.skills(id) ON DELETE CASCADE,
|
|
skill_level_id UUID REFERENCES hr.skill_levels(id),
|
|
skill_type_id UUID REFERENCES hr.skill_types(id),
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(employee_id, skill_id)
|
|
);
|
|
|
|
CREATE INDEX idx_skill_types_tenant ON hr.skill_types(tenant_id);
|
|
CREATE INDEX idx_skills_tenant ON hr.skills(tenant_id);
|
|
CREATE INDEX idx_skills_type ON hr.skills(skill_type_id);
|
|
CREATE INDEX idx_skill_levels_type ON hr.skill_levels(skill_type_id);
|
|
CREATE INDEX idx_employee_skills_employee ON hr.employee_skills(employee_id);
|
|
CREATE INDEX idx_employee_skills_skill ON hr.employee_skills(skill_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.skill_types ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.skills ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.skill_levels ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_skill_types ON hr.skill_types
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
CREATE POLICY tenant_isolation_skills ON hr.skills
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
CREATE POLICY tenant_isolation_skill_levels ON hr.skill_levels
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
COMMENT ON TABLE hr.skill_types IS 'COR-063: Skill type categories';
|
|
COMMENT ON TABLE hr.skills IS 'COR-063: Individual skills';
|
|
COMMENT ON TABLE hr.skill_levels IS 'COR-063: Skill proficiency levels';
|
|
COMMENT ON TABLE hr.employee_skills IS 'COR-063: Employee skill assignments';
|
|
|
|
-- =====================================================
|
|
-- COR-064: Expense Reports
|
|
-- Reporte de gastos de empleados
|
|
-- =====================================================
|
|
|
|
CREATE TYPE hr.expense_status AS ENUM (
|
|
'draft',
|
|
'submitted',
|
|
'approved',
|
|
'posted',
|
|
'paid',
|
|
'rejected'
|
|
);
|
|
|
|
CREATE TABLE hr.expense_sheets (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
state hr.expense_status DEFAULT 'draft',
|
|
|
|
-- Montos
|
|
total_amount DECIMAL(20,6) DEFAULT 0,
|
|
untaxed_amount DECIMAL(20,6) DEFAULT 0,
|
|
total_amount_taxes DECIMAL(20,6) DEFAULT 0,
|
|
|
|
-- Cuenta contable
|
|
journal_id UUID, -- FK to financial.journals
|
|
account_move_id UUID, -- FK to financial.journal_entries
|
|
|
|
-- Aprobacion
|
|
user_id UUID REFERENCES auth.users(id), -- Responsable
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
approved_date TIMESTAMP WITH TIME ZONE,
|
|
|
|
-- Fechas
|
|
accounting_date DATE,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE hr.expenses (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
sheet_id UUID REFERENCES hr.expense_sheets(id) ON DELETE SET NULL,
|
|
|
|
-- Producto/Categoria de gasto
|
|
product_id UUID REFERENCES inventory.products(id),
|
|
|
|
-- Montos
|
|
unit_amount DECIMAL(20,6) NOT NULL,
|
|
quantity DECIMAL(20,6) DEFAULT 1,
|
|
total_amount DECIMAL(20,6) NOT NULL,
|
|
untaxed_amount DECIMAL(20,6),
|
|
total_amount_taxes DECIMAL(20,6),
|
|
currency_id UUID REFERENCES core.currencies(id),
|
|
|
|
-- Impuestos
|
|
tax_ids UUID[] DEFAULT '{}',
|
|
|
|
-- Fechas
|
|
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
|
|
-- Documentacion
|
|
description TEXT,
|
|
reference VARCHAR(255),
|
|
|
|
-- Analitica
|
|
analytic_account_id UUID REFERENCES analytics.analytic_accounts(id),
|
|
|
|
-- Estado
|
|
state hr.expense_status DEFAULT 'draft',
|
|
payment_mode VARCHAR(50) DEFAULT 'own_account', -- own_account, company_account
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_expense_sheets_tenant ON hr.expense_sheets(tenant_id);
|
|
CREATE INDEX idx_expense_sheets_employee ON hr.expense_sheets(employee_id);
|
|
CREATE INDEX idx_expense_sheets_state ON hr.expense_sheets(state);
|
|
CREATE INDEX idx_expenses_tenant ON hr.expenses(tenant_id);
|
|
CREATE INDEX idx_expenses_employee ON hr.expenses(employee_id);
|
|
CREATE INDEX idx_expenses_sheet ON hr.expenses(sheet_id);
|
|
CREATE INDEX idx_expenses_date ON hr.expenses(date);
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.expense_sheets ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.expenses ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_expense_sheets ON hr.expense_sheets
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
CREATE POLICY tenant_isolation_expenses ON hr.expenses
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
COMMENT ON TABLE hr.expense_sheets IS 'COR-064: Expense reports';
|
|
COMMENT ON TABLE hr.expenses IS 'COR-064: Individual expense lines';
|
|
|
|
-- =====================================================
|
|
-- COR-065: Employee Resume Lines
|
|
-- Historial de experiencia y educacion
|
|
-- =====================================================
|
|
|
|
CREATE TYPE hr.resume_line_type AS ENUM (
|
|
'experience',
|
|
'education',
|
|
'certification',
|
|
'internal'
|
|
);
|
|
|
|
CREATE TABLE hr.employee_resume_lines (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
date_start DATE,
|
|
date_end DATE,
|
|
description TEXT,
|
|
line_type hr.resume_line_type NOT NULL,
|
|
|
|
-- Para experiencia laboral
|
|
company_name VARCHAR(255),
|
|
job_title VARCHAR(255),
|
|
|
|
-- Para educacion
|
|
institution VARCHAR(255),
|
|
degree VARCHAR(255),
|
|
field_of_study VARCHAR(255),
|
|
|
|
-- Para certificaciones
|
|
certification_name VARCHAR(255),
|
|
certification_id VARCHAR(100),
|
|
expiry_date DATE,
|
|
|
|
-- Orden
|
|
display_type VARCHAR(50), -- classic, course
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_employee_resume_lines_employee ON hr.employee_resume_lines(employee_id);
|
|
CREATE INDEX idx_employee_resume_lines_type ON hr.employee_resume_lines(line_type);
|
|
|
|
COMMENT ON TABLE hr.employee_resume_lines IS 'COR-065: Employee resume/CV lines';
|
|
|
|
-- =====================================================
|
|
-- COR-066: Payslip Basics
|
|
-- Estructura basica de nomina (sin calculos complejos)
|
|
-- =====================================================
|
|
|
|
CREATE TYPE hr.payslip_status AS ENUM (
|
|
'draft',
|
|
'verify',
|
|
'done',
|
|
'cancel'
|
|
);
|
|
|
|
CREATE TABLE hr.payslip_structures (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(50),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
note TEXT,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
CREATE TABLE hr.payslips (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
contract_id UUID REFERENCES hr.contracts(id),
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
number VARCHAR(100),
|
|
state hr.payslip_status DEFAULT 'draft',
|
|
|
|
-- Periodo
|
|
date_from DATE NOT NULL,
|
|
date_to DATE NOT NULL,
|
|
date DATE, -- Fecha de pago
|
|
|
|
-- Estructura
|
|
structure_id UUID REFERENCES hr.payslip_structures(id),
|
|
|
|
-- Montos
|
|
basic_wage DECIMAL(20,6),
|
|
gross_wage DECIMAL(20,6),
|
|
net_wage DECIMAL(20,6),
|
|
|
|
-- Horas
|
|
worked_days DECIMAL(10,2),
|
|
worked_hours DECIMAL(10,2),
|
|
|
|
-- Contabilidad
|
|
journal_id UUID, -- FK to financial.journals
|
|
move_id UUID, -- FK to financial.journal_entries
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE hr.payslip_lines (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
payslip_id UUID NOT NULL REFERENCES hr.payslips(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
code VARCHAR(50) NOT NULL, -- BASIC, GROSS, NET, etc.
|
|
sequence INTEGER DEFAULT 10,
|
|
|
|
-- Tipo de linea
|
|
category VARCHAR(50), -- earning, deduction, company_contribution
|
|
|
|
-- Montos
|
|
quantity DECIMAL(20,6) DEFAULT 1,
|
|
rate DECIMAL(10,4) DEFAULT 100,
|
|
amount DECIMAL(20,6) NOT NULL DEFAULT 0,
|
|
total DECIMAL(20,6) NOT NULL DEFAULT 0,
|
|
|
|
-- Control
|
|
appears_on_payslip BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Auditoria
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_payslip_structures_tenant ON hr.payslip_structures(tenant_id);
|
|
CREATE INDEX idx_payslips_tenant ON hr.payslips(tenant_id);
|
|
CREATE INDEX idx_payslips_employee ON hr.payslips(employee_id);
|
|
CREATE INDEX idx_payslips_contract ON hr.payslips(contract_id);
|
|
CREATE INDEX idx_payslips_state ON hr.payslips(state);
|
|
CREATE INDEX idx_payslips_period ON hr.payslips(date_from, date_to);
|
|
CREATE INDEX idx_payslip_lines_payslip ON hr.payslip_lines(payslip_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.payslip_structures ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.payslips ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_payslip_structures ON hr.payslip_structures
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
CREATE POLICY tenant_isolation_payslips ON hr.payslips
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
COMMENT ON TABLE hr.payslip_structures IS 'COR-066: Payslip structures';
|
|
COMMENT ON TABLE hr.payslips IS 'COR-066: Employee payslips';
|
|
COMMENT ON TABLE hr.payslip_lines IS 'COR-066: Payslip detail lines';
|
|
|
|
-- =====================================================
|
|
-- FIN DEL SCHEMA HR
|
|
-- =====================================================
|