panels-origin/db/core/changesets/050-imss-wage.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

129 lines
7 KiB
PL/PgSQL

--liquibase formatted sql
-- PANELS · core · jornal IMSS y consulta de cuotas
--changeset panel:core-050a-columns endDelimiter:; splitStatements:true
ALTER TABLE core.workers ADD COLUMN IF NOT EXISTS imss_daily_wage NUMERIC(12,2);
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS imss_rate_enfermedad NUMERIC(8,6) NOT NULL DEFAULT 0.020000;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS imss_rate_invalidez NUMERIC(8,6) NOT NULL DEFAULT 0.017500;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS imss_rate_retiro NUMERIC(8,6) NOT NULL DEFAULT 0.020000;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS imss_rate_cesantia NUMERIC(8,6) NOT NULL DEFAULT 0.031500;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS imss_rate_infonavit NUMERIC(8,6) NOT NULL DEFAULT 0.050000;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS imss_rate_riesgo NUMERIC(8,6) NOT NULL DEFAULT 0.005433;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS settlement_aguinaldo_days NUMERIC(6,2) NOT NULL DEFAULT 15;
ALTER TABLE core.payroll_settings ADD COLUMN IF NOT EXISTS settlement_prima_pct NUMERIC(6,2) NOT NULL DEFAULT 25;
--changeset panel:core-050b-fn-settings-save splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_payroll_settings_save(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_tid integer := core._payroll_tenant_id(payload);
v_cur payroll_settings%ROWTYPE;
v_settings jsonb;
BEGIN
PERFORM core._payroll_seed_defaults(v_tid);
SELECT * INTO v_cur FROM payroll_settings WHERE tenant_id = v_tid;
UPDATE payroll_settings SET
loan_commission_enabled = COALESCE((payload->>'loan_commission_enabled')::boolean, v_cur.loan_commission_enabled),
loan_commission_pct = COALESCE((payload->>'loan_commission_pct')::numeric, v_cur.loan_commission_pct, 0),
loan_small_max = COALESCE((payload->>'loan_small_max')::numeric, v_cur.loan_small_max, 0),
imss_rate_enfermedad = COALESCE((payload->>'imss_rate_enfermedad')::numeric, v_cur.imss_rate_enfermedad),
imss_rate_invalidez = COALESCE((payload->>'imss_rate_invalidez')::numeric, v_cur.imss_rate_invalidez),
imss_rate_retiro = COALESCE((payload->>'imss_rate_retiro')::numeric, v_cur.imss_rate_retiro),
imss_rate_cesantia = COALESCE((payload->>'imss_rate_cesantia')::numeric, v_cur.imss_rate_cesantia),
imss_rate_infonavit = COALESCE((payload->>'imss_rate_infonavit')::numeric, v_cur.imss_rate_infonavit),
imss_rate_riesgo = COALESCE((payload->>'imss_rate_riesgo')::numeric, v_cur.imss_rate_riesgo),
settlement_aguinaldo_days = COALESCE((payload->>'settlement_aguinaldo_days')::numeric, v_cur.settlement_aguinaldo_days),
settlement_prima_pct = COALESCE((payload->>'settlement_prima_pct')::numeric, v_cur.settlement_prima_pct)
WHERE tenant_id = v_tid;
SELECT to_jsonb(s) INTO v_settings FROM payroll_settings s WHERE tenant_id = v_tid;
RETURN core.rpc_ok(jsonb_build_object('settings', v_settings), 'Configuración de nómina guardada',
jsonb_build_object('fn', 'fn_payroll_settings_save', 'tenant_id', v_tid));
EXCEPTION WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_payroll_settings_save', SQLSTATE, SQLERRM);
END;
$$;
--changeset panel:core-050c-fn-imss-quote splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_imss_quote(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_tid integer := core._payroll_tenant_id(payload);
v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint;
v_from date := NULLIF(payload->>'date_from', '')::date;
v_to date := NULLIF(payload->>'date_to', '')::date;
v_settings payroll_settings%ROWTYPE;
v_rows jsonb;
BEGIN
IF v_from IS NULL OR v_to IS NULL THEN
RETURN core.rpc_err('VALIDATION', 'fn_imss_quote: indique el periodo',
jsonb_build_object('fn', 'fn_imss_quote'));
END IF;
PERFORM core._payroll_seed_defaults(v_tid);
SELECT * INTO v_settings FROM payroll_settings WHERE tenant_id = v_tid;
SELECT COALESCE(jsonb_agg(to_jsonb(x) ORDER BY x.last_name_p), '[]'::jsonb) INTO v_rows
FROM (
SELECT w.id AS worker_id, w.first_name, w.last_name_p, w.nss, w.daily_wage,
w.imss_daily_wage,
COALESCE(w.imss_daily_wage, w.daily_wage) AS wage_used,
(w.imss_daily_wage IS NULL) AS used_real_wage,
COALESCE(d.days, 0) AS days,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage)) AS sbc,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage) * v_settings.imss_rate_enfermedad) AS cuota_enfermedad,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage) * v_settings.imss_rate_invalidez) AS cuota_invalidez,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage) * v_settings.imss_rate_retiro) AS cuota_retiro,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage) * v_settings.imss_rate_cesantia) AS cuota_cesantia,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage) * v_settings.imss_rate_infonavit) AS cuota_infonavit,
core.round_money(COALESCE(d.days, 0) * COALESCE(w.imss_daily_wage, w.daily_wage) * v_settings.imss_rate_riesgo) AS cuota_riesgo
FROM workers w
LEFT JOIN (
SELECT worker_id, COUNT(*)::numeric AS days
FROM attendance
WHERE present = true AND work_date BETWEEN v_from AND v_to
GROUP BY worker_id
) d ON d.worker_id = w.id
WHERE w.tenant_id = v_tid
AND (v_worker_id IS NULL OR w.id = v_worker_id)
) x;
RETURN core.rpc_ok(
jsonb_build_object('quotes', v_rows, 'rates', to_jsonb(v_settings)),
'Consulta de cuotas IMSS',
jsonb_build_object('fn', 'fn_imss_quote')
);
EXCEPTION WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_imss_quote', SQLSTATE, SQLERRM);
END;
$$;
--changeset panel:core-050d-fn-imss-wage splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_worker_imss_wage_set(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_id bigint := NULLIF(payload->>'id', '')::bigint;
v_wage numeric := NULLIF(payload->>'imss_daily_wage', '')::numeric;
BEGIN
UPDATE workers SET imss_daily_wage = v_wage, updated_at = now() WHERE id = v_id;
IF NOT FOUND THEN
RETURN core.rpc_err('NOT_FOUND', 'Trabajador no encontrado', jsonb_build_object('fn', 'fn_worker_imss_wage_set', 'id', v_id));
END IF;
RETURN core.rpc_ok(jsonb_build_object('id', v_id, 'imss_daily_wage', v_wage), 'Jornal IMSS actualizado',
jsonb_build_object('fn', 'fn_worker_imss_wage_set', 'id', v_id));
EXCEPTION WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_worker_imss_wage_set', SQLSTATE, SQLERRM);
END;
$$;
--changeset panel:core-050e-grants endDelimiter:; splitStatements:true
GRANT EXECUTE ON FUNCTION core.fn_imss_quote(jsonb) TO panels_core_app;
GRANT EXECUTE ON FUNCTION core.fn_worker_imss_wage_set(jsonb) TO panels_core_app;