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