panels-origin/db/core/changesets/042-suppliers-fiscal.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

160 lines
7.6 KiB
PL/PgSQL

--liquibase formatted sql
-- PANELS · core · datos fiscales de proveedores y filtro de listado
--changeset panel:core-042a-supplier-fiscal-cols endDelimiter:; splitStatements:true
ALTER TABLE core.suppliers
ADD COLUMN IF NOT EXISTS regimen_fiscal TEXT NOT NULL DEFAULT '';
--changeset panel:core-042b-fn-supplier-list splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_supplier_list(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_tid integer := core._cost_tenant_id(payload);
v_status text := nullif(btrim(payload->>'status'), '');
v_q text := lower(coalesce(nullif(btrim(payload->>'q'), ''), ''));
v_rows jsonb;
BEGIN
SELECT COALESCE(jsonb_agg(to_jsonb(s) ORDER BY lower(s.name), s.id), '[]'::jsonb)
INTO v_rows
FROM suppliers s
WHERE s.tenant_id = v_tid
AND (v_status IS NULL OR s.status = v_status)
AND (
v_q = ''
OR lower(s.name) LIKE '%' || v_q || '%'
OR lower(s.legal_name) LIKE '%' || v_q || '%'
OR lower(s.rfc) LIKE '%' || v_q || '%'
);
RETURN core.rpc_ok(
jsonb_build_object('suppliers', v_rows, 'count', jsonb_array_length(v_rows)),
format('Catálogo de proveedores: %s registro(s) para tenant %s', jsonb_array_length(v_rows), v_tid),
jsonb_build_object('fn', 'fn_supplier_list', 'tenant_id', v_tid, 'count', jsonb_array_length(v_rows))
);
EXCEPTION WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_supplier_list', SQLSTATE, SQLERRM);
END;
$$;
--changeset panel:core-042c-fn-supplier-create splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_supplier_create(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_tid integer := core._cost_tenant_id(payload);
v_name text := coalesce(nullif(btrim(payload->>'name'), ''), '');
v_legal text := coalesce(nullif(btrim(payload->>'legal_name'), ''), '');
v_rfc text := upper(regexp_replace(coalesce(payload->>'rfc', ''), '\s+', '', 'g'));
v_email text := coalesce(nullif(btrim(payload->>'email'), ''), '');
v_phone text := coalesce(nullif(btrim(payload->>'phone'), ''), '');
v_address text := coalesce(nullif(btrim(payload->>'address'), ''), '');
v_city text := coalesce(nullif(btrim(payload->>'city'), ''), '');
v_state text := coalesce(nullif(btrim(payload->>'state'), ''), '');
v_zip text := coalesce(nullif(btrim(payload->>'postal_code'), ''), '');
v_regimen text := coalesce(nullif(btrim(payload->>'regimen_fiscal'), ''), '');
v_notes text := coalesce(nullif(btrim(payload->>'notes'), ''), '');
v_status text := coalesce(nullif(btrim(payload->>'status'), ''), 'activo');
v_id bigint;
v_row jsonb;
BEGIN
IF v_name = '' THEN
RETURN core.rpc_err('VALIDATION', 'fn_supplier_create: el nombre del proveedor es obligatorio',
jsonb_build_object('fn', 'fn_supplier_create', 'field', 'name'));
END IF;
IF v_rfc <> '' AND char_length(v_rfc) NOT IN (12, 13) THEN
RETURN core.rpc_err('VALIDATION', format('fn_supplier_create: RFC %s no tiene 12 o 13 caracteres', v_rfc),
jsonb_build_object('fn', 'fn_supplier_create', 'field', 'rfc'));
END IF;
IF v_status NOT IN ('activo', 'inactivo') THEN
RETURN core.rpc_err('VALIDATION', 'fn_supplier_create: status debe ser activo o inactivo',
jsonb_build_object('fn', 'fn_supplier_create', 'field', 'status'));
END IF;
INSERT INTO suppliers (
tenant_id, name, legal_name, rfc, email, phone, address, city, state, postal_code,
regimen_fiscal, notes, status
) VALUES (
v_tid, v_name, v_legal, v_rfc, v_email, v_phone, v_address, v_city, v_state, v_zip,
v_regimen, v_notes, v_status
) RETURNING id INTO v_id;
SELECT to_jsonb(s) INTO v_row FROM suppliers s WHERE id = v_id;
RETURN core.rpc_created(
jsonb_build_object('supplier', v_row),
format('Proveedor %s registrado', v_name),
jsonb_build_object('fn', 'fn_supplier_create', 'id', v_id, 'tenant_id', v_tid)
);
EXCEPTION
WHEN unique_violation THEN
RETURN core.rpc_err('CONFLICT', format('Ya existe un proveedor con RFC %s en este tenant', v_rfc),
jsonb_build_object('fn', 'fn_supplier_create', 'rfc', v_rfc));
WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_supplier_create', SQLSTATE, SQLERRM);
END;
$$;
--changeset panel:core-042d-fn-supplier-update splitStatements:false
CREATE OR REPLACE FUNCTION core.fn_supplier_update(payload jsonb)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = core
AS $$
DECLARE
v_id bigint := NULLIF(payload->>'id', '')::bigint;
v_tid integer := core._cost_tenant_id(payload);
v_rfc text;
v_status text;
v_row jsonb;
BEGIN
IF v_id IS NULL THEN
RETURN core.rpc_err('VALIDATION', 'fn_supplier_update: id es obligatorio',
jsonb_build_object('fn', 'fn_supplier_update', 'field', 'id'));
END IF;
IF NOT EXISTS (SELECT 1 FROM suppliers WHERE id = v_id AND tenant_id = v_tid) THEN
RETURN core.rpc_err('NOT_FOUND', format('fn_supplier_update: proveedor id=%s no encontrado', v_id),
jsonb_build_object('fn', 'fn_supplier_update', 'id', v_id));
END IF;
v_rfc := upper(regexp_replace(coalesce(payload->>'rfc', ''), '\s+', '', 'g'));
IF payload ? 'rfc' AND v_rfc <> '' AND char_length(v_rfc) NOT IN (12, 13) THEN
RETURN core.rpc_err('VALIDATION', format('fn_supplier_update: RFC %s no tiene 12 o 13 caracteres', v_rfc),
jsonb_build_object('fn', 'fn_supplier_update', 'field', 'rfc'));
END IF;
v_status := nullif(btrim(payload->>'status'), '');
IF payload ? 'status' AND v_status NOT IN ('activo', 'inactivo') THEN
RETURN core.rpc_err('VALIDATION', 'fn_supplier_update: status debe ser activo o inactivo',
jsonb_build_object('fn', 'fn_supplier_update', 'field', 'status'));
END IF;
UPDATE suppliers SET
name = CASE WHEN payload ? 'name' THEN coalesce(nullif(btrim(payload->>'name'), ''), name) ELSE name END,
legal_name = CASE WHEN payload ? 'legal_name' THEN coalesce(nullif(btrim(payload->>'legal_name'), ''), '') ELSE legal_name END,
rfc = CASE WHEN payload ? 'rfc' THEN v_rfc ELSE rfc END,
email = CASE WHEN payload ? 'email' THEN coalesce(nullif(btrim(payload->>'email'), ''), '') ELSE email END,
phone = CASE WHEN payload ? 'phone' THEN coalesce(nullif(btrim(payload->>'phone'), ''), '') ELSE phone END,
address = CASE WHEN payload ? 'address' THEN coalesce(nullif(btrim(payload->>'address'), ''), '') ELSE address END,
city = CASE WHEN payload ? 'city' THEN coalesce(nullif(btrim(payload->>'city'), ''), '') ELSE city END,
state = CASE WHEN payload ? 'state' THEN coalesce(nullif(btrim(payload->>'state'), ''), '') ELSE state END,
postal_code = CASE WHEN payload ? 'postal_code' THEN coalesce(nullif(btrim(payload->>'postal_code'), ''), '') ELSE postal_code END,
regimen_fiscal = CASE WHEN payload ? 'regimen_fiscal' THEN coalesce(nullif(btrim(payload->>'regimen_fiscal'), ''), '') ELSE regimen_fiscal END,
notes = CASE WHEN payload ? 'notes' THEN coalesce(nullif(btrim(payload->>'notes'), ''), '') ELSE notes END,
status = CASE WHEN payload ? 'status' THEN v_status ELSE status END,
updated_at = now()
WHERE id = v_id AND tenant_id = v_tid;
SELECT to_jsonb(s) INTO v_row FROM suppliers s WHERE id = v_id;
RETURN core.rpc_ok(
jsonb_build_object('supplier', v_row),
format('Proveedor id=%s actualizado', v_id),
jsonb_build_object('fn', 'fn_supplier_update', 'id', v_id)
);
EXCEPTION
WHEN unique_violation THEN
RETURN core.rpc_err('CONFLICT', 'Ya existe otro proveedor con ese RFC en este tenant',
jsonb_build_object('fn', 'fn_supplier_update', 'id', v_id));
WHEN OTHERS THEN
RETURN core.rpc_from_exception('fn_supplier_update', SQLSTATE, SQLERRM);
END;
$$;