panels-origin/db/core/changesets/048-worker-movements.sql
Cursor Agent caf40e29fb
feat: facturas, ajustes de nómina, finiquitos, dispersión e IMSS
Reutiliza el catálogo de proveedores y agrega carga de facturas, conceptos de la semana, préstamos editables, finiquitos, dispersión, historial de personal, reportes y jornal de cotización.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-24 16:40:08 +00:00

104 lines
5 KiB
PL/PgSQL

--liquibase formatted sql
-- PANELS · core · historial de alta, baja y reingreso
--changeset panel:core-048a-table endDelimiter:; splitStatements:true
ALTER TABLE core.workers ADD COLUMN IF NOT EXISTS imss_daily_wage NUMERIC(12,2);
CREATE TABLE IF NOT EXISTS core.worker_movements (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id INTEGER,
worker_id BIGINT NOT NULL REFERENCES core.workers(id) ON DELETE CASCADE,
kind TEXT NOT NULL CHECK (kind IN ('alta', 'baja', 'reingreso', 'alta_imss', 'baja_imss', 'modificacion_salario')),
movement_date DATE NOT NULL,
note TEXT NOT NULL DEFAULT '',
actor_name TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_core_worker_movements_worker ON core.worker_movements (worker_id, movement_date, id);
GRANT SELECT, INSERT, UPDATE, DELETE ON core.worker_movements TO panels_core_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA core TO panels_core_app;
--changeset panel:core-048b-backfill endDelimiter:; splitStatements:true
INSERT INTO core.worker_movements (tenant_id, worker_id, kind, movement_date, note)
SELECT w.tenant_id, w.id, 'alta', COALESCE(w.created_at::date, CURRENT_DATE), 'Alta de padrón'
FROM core.workers w
WHERE NOT EXISTS (
SELECT 1 FROM core.worker_movements m WHERE m.worker_id = w.id AND m.kind = 'alta'
);
INSERT INTO core.worker_movements (tenant_id, worker_id, kind, movement_date, note)
SELECT w.tenant_id, w.id, 'reingreso', w.last_rehire_at, 'Reingreso'
FROM core.workers w
WHERE w.last_rehire_at IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM core.worker_movements m WHERE m.worker_id = w.id AND m.kind = 'reingreso' AND m.movement_date = w.last_rehire_at
);
INSERT INTO core.worker_movements (tenant_id, worker_id, kind, movement_date, note)
SELECT w.tenant_id, w.id, 'baja', COALESCE(w.updated_at::date, CURRENT_DATE), 'Baja de padrón'
FROM core.workers w
WHERE w.status = 'baja'
AND NOT EXISTS (SELECT 1 FROM core.worker_movements m WHERE m.worker_id = w.id AND m.kind = 'baja');
--changeset panel:core-048c-trigger splitStatements:false
CREATE OR REPLACE FUNCTION core.trg_worker_movement()
RETURNS trigger
LANGUAGE plpgsql
SET search_path = core
AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO worker_movements (tenant_id, worker_id, kind, movement_date, note)
VALUES (NEW.tenant_id, NEW.id, 'alta', COALESCE(NEW.created_at::date, CURRENT_DATE), 'Alta de padrón');
RETURN NEW;
END IF;
IF OLD.status IS DISTINCT FROM 'baja' AND NEW.status = 'baja' THEN
INSERT INTO worker_movements (tenant_id, worker_id, kind, movement_date, note)
VALUES (NEW.tenant_id, NEW.id, 'baja', CURRENT_DATE, 'Baja de padrón');
ELSIF OLD.status = 'baja' AND NEW.status = 'activo' THEN
INSERT INTO worker_movements (tenant_id, worker_id, kind, movement_date, note)
VALUES (NEW.tenant_id, NEW.id, 'reingreso', COALESCE(NEW.last_rehire_at, CURRENT_DATE), 'Reingreso');
END IF;
IF OLD.imss_status IS DISTINCT FROM 'alta' AND NEW.imss_status = 'alta' THEN
INSERT INTO worker_movements (tenant_id, worker_id, kind, movement_date, note)
VALUES (NEW.tenant_id, NEW.id, 'alta_imss', COALESCE(NEW.imss_alta_at, CURRENT_DATE), 'Alta IMSS');
ELSIF OLD.imss_status IS DISTINCT FROM 'baja_imss' AND NEW.imss_status = 'baja_imss' THEN
INSERT INTO worker_movements (tenant_id, worker_id, kind, movement_date, note)
VALUES (NEW.tenant_id, NEW.id, 'baja_imss', COALESCE(NEW.imss_baja_at, CURRENT_DATE), 'Baja IMSS');
END IF;
IF NEW.imss_daily_wage IS NOT NULL AND NEW.imss_daily_wage IS DISTINCT FROM OLD.imss_daily_wage THEN
INSERT INTO worker_movements (tenant_id, worker_id, kind, movement_date, note)
VALUES (NEW.tenant_id, NEW.id, 'modificacion_salario', CURRENT_DATE,
format('Jornal IMSS %s', NEW.imss_daily_wage));
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_worker_movement ON core.workers;
CREATE TRIGGER trg_worker_movement
AFTER INSERT OR UPDATE OF status, imss_status, imss_daily_wage ON core.workers
FOR EACH ROW EXECUTE FUNCTION core.trg_worker_movement();
--changeset panel:core-048d-fn-list splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_worker_movement_list(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
v_rows jsonb;
BEGIN
SELECT COALESCE(jsonb_agg(to_jsonb(m) ORDER BY m.movement_date, m.id), '[]'::jsonb)
INTO v_rows
FROM worker_movements m
WHERE m.worker_id = v_worker_id;
RETURN core.rpc_ok(jsonb_build_object('movements', v_rows), 'Historial de movimientos',
jsonb_build_object('fn', 'fn_worker_movement_list', 'worker_id', v_worker_id));
EXCEPTION WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_worker_movement_list', SQLSTATE, SQLERRM);
END;
$$;
--changeset panel:core-048e-grants endDelimiter:; splitStatements:true
GRANT EXECUTE ON FUNCTION core.trg_worker_movement() TO panels_core_app;
GRANT EXECUTE ON FUNCTION core.fn_worker_movement_list(jsonb) TO panels_core_app;