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