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