From 02098d37b3b44e72e70b4ee87b24968e06de78a0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 3 Sep 2026 23:48:29 +0000 Subject: [PATCH] Add PostgreSQL RPC functions for companies CRUD Implement fn_company_list, fn_company_get, fn_company_get_by_code, fn_company_create, fn_company_update, and fn_next_company_code with shared validation helpers mirroring api/companies.ts logic. Uses RPC envelope helpers from 006-rpc-infra.sql with detailed Spanish error messages and GRANT EXECUTE for panels_core_app. Co-authored-by: alberto.martinez --- db/core/changesets/008-rpc-companies.sql | 639 +++++++++++++++++++++++ 1 file changed, 639 insertions(+) create mode 100644 db/core/changesets/008-rpc-companies.sql diff --git a/db/core/changesets/008-rpc-companies.sql b/db/core/changesets/008-rpc-companies.sql new file mode 100644 index 0000000..09c7627 --- /dev/null +++ b/db/core/changesets/008-rpc-companies.sql @@ -0,0 +1,639 @@ +--liquibase formatted sql +-- PANELS · core · RPC empresas (CRUD) + +--changeset panel:core-008a-company-helpers endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core._company_trim_text(p_value text) +RETURNS text +LANGUAGE sql +IMMUTABLE +AS $$ + SELECT btrim(COALESCE(p_value, '')); +$$; + +CREATE OR REPLACE FUNCTION core._company_norm_code(p_value text) +RETURNS text +LANGUAGE sql +IMMUTABLE +AS $$ + SELECT upper(regexp_replace(btrim(COALESCE(p_value, '')), '\s+', '', 'g')); +$$; + +CREATE OR REPLACE FUNCTION core._company_norm_rfc(p_value text) +RETURNS text +LANGUAGE sql +IMMUTABLE +AS $$ + SELECT upper(regexp_replace(btrim(COALESCE(p_value, '')), '\s+', '', 'g')); +$$; + +CREATE OR REPLACE FUNCTION core._company_code_from_name(p_name text) +RETURNS text +LANGUAGE sql +IMMUTABLE +AS $$ + SELECT COALESCE( + NULLIF( + left( + upper( + regexp_replace( + translate( + COALESCE(p_name, ''), + 'áàäâãåéèëêíìïîóòöôõúùüûñÁÀÄÂÃÅÉÈËÊÍÌÏÎÓÒÖÔÕÚÙÜÛÑ', + 'aaaaaaeeeeiiiiooooouuuunAAAAAAEEEEIIIIOOOOOUUUUN' + ), + '[^A-Z0-9]+', + '', + 'g' + ) + ), + 12 + ), + '' + ), + 'EMP' + ); +$$; + +CREATE OR REPLACE FUNCTION core._company_validate_code(p_code text) +RETURNS text +LANGUAGE plpgsql +IMMUTABLE +AS $$ +BEGIN + IF p_code IS NULL OR p_code = '' THEN + RETURN 'Código de 2 a 16 caracteres (letras, números, _ o -)'; + END IF; + IF p_code !~ '^[A-Z0-9][A-Z0-9_-]{1,15}$' THEN + RETURN 'Código de 2 a 16 caracteres (letras, números, _ o -)'; + END IF; + RETURN NULL; +END; +$$; + +CREATE OR REPLACE FUNCTION core._company_validate_profile( + p_payload jsonb, + p_require_legal boolean DEFAULT false +) +RETURNS text +LANGUAGE plpgsql +IMMUTABLE +AS $$ +DECLARE + v_rfc text := core._company_norm_rfc(p_payload->>'rfc'); + v_clase text := upper(core._company_trim_text(p_payload->>'clase_riesgo')); +BEGIN + IF v_rfc <> '' AND v_rfc !~ '^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$' THEN + RETURN 'RFC inválido (formato mexicano de 12 o 13 caracteres)'; + END IF; + IF p_require_legal THEN + IF core._company_trim_text(p_payload->>'razon_social') = '' + AND core._company_trim_text(p_payload->>'name') = '' + AND core._company_trim_text(p_payload->>'nombre_comercial') = '' THEN + RETURN 'Indique razón social o nombre comercial'; + END IF; + END IF; + IF v_clase <> '' AND v_clase NOT IN ('I', 'II', 'III', 'IV', 'V') THEN + RETURN 'Clase de riesgo IMSS debe ser I, II, III, IV o V'; + END IF; + RETURN NULL; +END; +$$; + +CREATE OR REPLACE FUNCTION core._company_normalize_profile( + p_payload jsonb, + p_fallback_name text DEFAULT '' +) +RETURNS jsonb +LANGUAGE plpgsql +IMMUTABLE +AS $$ +DECLARE + v_nombre_comercial text; + v_razon_social text; + v_name text; +BEGIN + v_nombre_comercial := COALESCE( + NULLIF(core._company_trim_text(p_payload->>'nombre_comercial'), ''), + NULLIF(core._company_trim_text(p_payload->>'name'), ''), + p_fallback_name + ); + v_razon_social := COALESCE( + NULLIF(core._company_trim_text(p_payload->>'razon_social'), ''), + NULLIF(v_nombre_comercial, ''), + p_fallback_name + ); + v_name := COALESCE( + NULLIF(core._company_trim_text(p_payload->>'name'), ''), + NULLIF(v_nombre_comercial, ''), + NULLIF(v_razon_social, ''), + p_fallback_name + ); + RETURN jsonb_build_object( + 'name', v_name, + 'nombre_comercial', v_nombre_comercial, + 'razon_social', v_razon_social, + 'rfc', core._company_norm_rfc(p_payload->>'rfc'), + 'regimen_fiscal', core._company_trim_text(p_payload->>'regimen_fiscal'), + 'registro_patronal', upper(core._company_trim_text(p_payload->>'registro_patronal')), + 'clase_riesgo', upper(core._company_trim_text(p_payload->>'clase_riesgo')), + 'domicilio_fiscal', core._company_trim_text(p_payload->>'domicilio_fiscal'), + 'codigo_postal', core._company_trim_text(p_payload->>'codigo_postal'), + 'ciudad', core._company_trim_text(p_payload->>'ciudad'), + 'estado', core._company_trim_text(p_payload->>'estado'), + 'telefono', core._company_trim_text(p_payload->>'telefono'), + 'email', lower(core._company_trim_text(p_payload->>'email')), + 'representante_legal', core._company_trim_text(p_payload->>'representante_legal'), + 'giro', core._company_trim_text(p_payload->>'giro') + ); +END; +$$; + +--changeset panel:core-008b-fn-company-list endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core.fn_company_list(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_tid integer := NULLIF(payload->>'tenant_id', '')::integer; + v_companies jsonb; + v_count integer; +BEGIN + SELECT COALESCE( + jsonb_agg(row_to_json(x)::jsonb ORDER BY + CASE x.kind WHEN 'principal' THEN 0 ELSE 1 END, + lower(x.name)), + '[]'::jsonb + ), + COUNT(*)::integer + INTO v_companies, v_count + FROM ( + SELECT c.*, + p.code AS parent_code, + p.name AS parent_name, + (SELECT COUNT(*) FROM workers w WHERE w.company_id = c.id) AS worker_count + FROM companies c + LEFT JOIN companies p ON p.id = c.parent_id + WHERE v_tid IS NULL OR c.tenant_id = v_tid + ) x; + RETURN core.rpc_ok( + jsonb_build_object('companies', v_companies), + format('Listado de empresas: %s registro(s)%s', + v_count, + CASE WHEN v_tid IS NOT NULL THEN format(' para tenant_id=%s', v_tid) ELSE '' END), + jsonb_build_object('fn', 'fn_company_list', 'tenant_id', v_tid, 'count', v_count) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_company_list', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-008c-fn-company-get endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core.fn_company_get(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_id bigint := NULLIF(payload->>'id', '')::bigint; + v_tid integer := NULLIF(payload->>'tenant_id', '')::integer; + v_company jsonb; +BEGIN + IF v_id IS NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_company_get: id es obligatorio para consultar una empresa', + jsonb_build_object('fn', 'fn_company_get', 'field', 'id') + ); + END IF; + SELECT to_jsonb(c) INTO v_company + FROM companies c + WHERE c.id = v_id + AND (v_tid IS NULL OR c.tenant_id IS NULL OR c.tenant_id = v_tid); + IF v_company IS NULL THEN + RETURN core.rpc_err( + 'NOT_FOUND', + format('fn_company_get: empresa id=%s no encontrada%s', v_id, + CASE WHEN v_tid IS NOT NULL THEN format(' en tenant_id=%s', v_tid) ELSE '' END), + jsonb_build_object('fn', 'fn_company_get', 'id', v_id, 'tenant_id', v_tid) + ); + END IF; + RETURN core.rpc_ok( + jsonb_build_object('company', v_company), + format('Empresa id=%s cargada (%s)', v_id, v_company->>'code'), + jsonb_build_object('fn', 'fn_company_get', 'id', v_id) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_company_get', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-008d-fn-company-get-by-code endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core.fn_company_get_by_code(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_code text := core._company_norm_code(payload->>'code'); + v_company jsonb; +BEGIN + IF v_code = '' THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_company_get_by_code: code es obligatorio para consultar una empresa', + jsonb_build_object('fn', 'fn_company_get_by_code', 'field', 'code') + ); + END IF; + SELECT to_jsonb(c) INTO v_company + FROM companies c + WHERE c.code = v_code; + IF v_company IS NULL THEN + RETURN core.rpc_err( + 'NOT_FOUND', + format('fn_company_get_by_code: empresa con código %s no encontrada', v_code), + jsonb_build_object('fn', 'fn_company_get_by_code', 'code', v_code) + ); + END IF; + RETURN core.rpc_ok( + jsonb_build_object('company', v_company), + format('Empresa con código %s cargada (id=%s)', v_code, v_company->>'id'), + jsonb_build_object('fn', 'fn_company_get_by_code', 'code', v_code) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_company_get_by_code', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-008e-fn-next-company-code endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core.fn_next_company_code(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_name text := core._company_trim_text(payload->>'name'); + v_base text; + v_n integer; + v_code text; +BEGIN + IF v_name = '' THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_next_company_code: name es obligatorio para generar un código de empresa', + jsonb_build_object('fn', 'fn_next_company_code', 'field', 'name') + ); + END IF; + v_base := core._company_code_from_name(v_name); + IF NOT EXISTS (SELECT 1 FROM companies WHERE code = v_base) THEN + v_code := v_base; + ELSE + SELECT COALESCE(MAX(substring(code FROM 5)::integer), 0) + 1 + INTO v_n + FROM companies + WHERE code ~ '^EMP-[0-9]{4}'; + v_code := 'EMP-' || lpad(v_n::text, 4, '0'); + END IF; + RETURN core.rpc_ok( + jsonb_build_object('code', v_code), + format('Código sugerido para "%s": %s', v_name, v_code), + jsonb_build_object('fn', 'fn_next_company_code', 'name', v_name, 'code', v_code) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_next_company_code', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-008f-fn-company-create endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core.fn_company_create(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_tid integer := NULLIF(payload->>'tenant_id', '')::integer; + v_profile_err text; + v_profile jsonb; + v_principal companies%ROWTYPE; + v_parent_id bigint; + v_parent companies%ROWTYPE; + v_code text; + v_code_err text; + v_new_id bigint; + v_n integer; + v_company jsonb; +BEGIN + v_profile_err := core._company_validate_profile(payload, true); + IF v_profile_err IS NOT NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_create: %s', v_profile_err), + jsonb_build_object('fn', 'fn_company_create', 'field', 'profile') + ); + END IF; + + v_profile := core._company_normalize_profile(payload, ''); + IF core._company_trim_text(v_profile->>'name') = '' THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_company_create: nombre de empresa obligatorio', + jsonb_build_object('fn', 'fn_company_create', 'field', 'name') + ); + END IF; + + IF v_tid IS NOT NULL THEN + SELECT * INTO v_principal + FROM companies + WHERE kind = 'principal' AND tenant_id = v_tid + ORDER BY id + LIMIT 1; + ELSE + SELECT * INTO v_principal + FROM companies + WHERE kind = 'principal' + ORDER BY id + LIMIT 1; + END IF; + IF NOT FOUND THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_company_create: no hay empresa principal configurada en el tenant', + jsonb_build_object('fn', 'fn_company_create', 'tenant_id', v_tid) + ); + END IF; + + v_parent_id := COALESCE(NULLIF(payload->>'parent_id', '')::bigint, v_principal.id); + SELECT * INTO v_parent FROM companies WHERE id = v_parent_id; + IF NOT FOUND THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_create: empresa padre id=%s no encontrada', v_parent_id), + jsonb_build_object('fn', 'fn_company_create', 'parent_id', v_parent_id) + ); + END IF; + IF v_tid IS NOT NULL AND v_parent.tenant_id IS NOT NULL AND v_parent.tenant_id <> v_tid THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_create: empresa padre id=%s pertenece a tenant_id=%s, distinto del solicitado (%s)', + v_parent_id, v_parent.tenant_id, v_tid), + jsonb_build_object('fn', 'fn_company_create', 'parent_id', v_parent_id, 'tenant_id', v_tid) + ); + END IF; + + v_code := core._company_norm_code(payload->>'code'); + IF v_code = '' THEN + v_code := core._company_code_from_name(v_profile->>'name'); + IF EXISTS (SELECT 1 FROM companies WHERE code = v_code) THEN + SELECT COALESCE(MAX(substring(code FROM 5)::integer), 0) + 1 + INTO v_n + FROM companies + WHERE code ~ '^EMP-[0-9]{4}'; + v_code := 'EMP-' || lpad(v_n::text, 4, '0'); + END IF; + END IF; + v_code_err := core._company_validate_code(v_code); + IF v_code_err IS NOT NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_create: %s (código=%s)', v_code_err, v_code), + jsonb_build_object('fn', 'fn_company_create', 'field', 'code', 'code', v_code) + ); + END IF; + IF EXISTS (SELECT 1 FROM companies WHERE code = v_code) THEN + RETURN core.rpc_err( + 'CONFLICT', + format('fn_company_create: ya existe una empresa con el código %s', v_code), + jsonb_build_object('fn', 'fn_company_create', 'code', v_code) + ); + END IF; + + INSERT INTO companies ( + code, name, parent_id, kind, status, tenant_id, + registro_patronal, razon_social, nombre_comercial, rfc, regimen_fiscal, clase_riesgo, + domicilio_fiscal, codigo_postal, ciudad, estado, telefono, email, representante_legal, giro + ) VALUES ( + v_code, + v_profile->>'name', + v_parent.id, + 'sub', + 'activo', + COALESCE(v_tid, v_principal.tenant_id), + v_profile->>'registro_patronal', + v_profile->>'razon_social', + v_profile->>'nombre_comercial', + v_profile->>'rfc', + v_profile->>'regimen_fiscal', + v_profile->>'clase_riesgo', + v_profile->>'domicilio_fiscal', + v_profile->>'codigo_postal', + v_profile->>'ciudad', + v_profile->>'estado', + v_profile->>'telefono', + v_profile->>'email', + v_profile->>'representante_legal', + v_profile->>'giro' + ) + RETURNING id INTO v_new_id; + + SELECT to_jsonb(c) INTO v_company FROM companies c WHERE c.id = v_new_id; + RETURN core.rpc_created( + jsonb_build_object('company', v_company), + format('Subempresa creada: %s (%s), id=%s', v_company->>'name', v_code, v_new_id), + jsonb_build_object('fn', 'fn_company_create', 'id', v_new_id, 'code', v_code, 'parent_id', v_parent.id) + ); +EXCEPTION + WHEN unique_violation THEN + RETURN core.rpc_err( + 'CONFLICT', + format('fn_company_create: ya existe una empresa con el código %s', v_code), + jsonb_build_object('fn', 'fn_company_create', 'code', v_code) + ); + WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_company_create', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-008g-fn-company-update endDelimiter:; splitStatements:true +CREATE OR REPLACE FUNCTION core.fn_company_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 := NULLIF(payload->>'tenant_id', '')::integer; + v_current companies%ROWTYPE; + v_merged jsonb; + v_profile_err text; + v_profile jsonb; + v_code text; + v_code_err text; + v_status text; + v_clash_id bigint; + v_company jsonb; +BEGIN + IF v_id IS NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_company_update: id es obligatorio para actualizar una empresa', + jsonb_build_object('fn', 'fn_company_update', 'field', 'id') + ); + END IF; + + SELECT * INTO v_current FROM companies WHERE id = v_id; + IF NOT FOUND THEN + RETURN core.rpc_err( + 'NOT_FOUND', + format('fn_company_update: empresa id=%s no encontrada', v_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'tenant_id', v_tid) + ); + END IF; + IF v_tid IS NOT NULL AND v_current.tenant_id IS NOT NULL AND v_current.tenant_id <> v_tid THEN + RETURN core.rpc_err( + 'NOT_FOUND', + format('fn_company_update: empresa id=%s no encontrada en tenant_id=%s', v_id, v_tid), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'tenant_id', v_tid) + ); + END IF; + + v_merged := jsonb_build_object( + 'name', CASE WHEN payload ? 'name' THEN payload->>'name' ELSE v_current.name END, + 'code', CASE WHEN payload ? 'code' THEN payload->>'code' ELSE v_current.code END, + 'status', CASE WHEN payload ? 'status' THEN payload->>'status' ELSE v_current.status END, + 'registro_patronal', CASE WHEN payload ? 'registro_patronal' THEN payload->>'registro_patronal' ELSE v_current.registro_patronal END, + 'razon_social', CASE WHEN payload ? 'razon_social' THEN payload->>'razon_social' ELSE v_current.razon_social END, + 'nombre_comercial', CASE WHEN payload ? 'nombre_comercial' THEN payload->>'nombre_comercial' ELSE v_current.nombre_comercial END, + 'rfc', CASE WHEN payload ? 'rfc' THEN payload->>'rfc' ELSE v_current.rfc END, + 'regimen_fiscal', CASE WHEN payload ? 'regimen_fiscal' THEN payload->>'regimen_fiscal' ELSE v_current.regimen_fiscal END, + 'clase_riesgo', CASE WHEN payload ? 'clase_riesgo' THEN payload->>'clase_riesgo' ELSE v_current.clase_riesgo END, + 'domicilio_fiscal', CASE WHEN payload ? 'domicilio_fiscal' THEN payload->>'domicilio_fiscal' ELSE v_current.domicilio_fiscal END, + 'codigo_postal', CASE WHEN payload ? 'codigo_postal' THEN payload->>'codigo_postal' ELSE v_current.codigo_postal END, + 'ciudad', CASE WHEN payload ? 'ciudad' THEN payload->>'ciudad' ELSE v_current.ciudad END, + 'estado', CASE WHEN payload ? 'estado' THEN payload->>'estado' ELSE v_current.estado END, + 'telefono', CASE WHEN payload ? 'telefono' THEN payload->>'telefono' ELSE v_current.telefono END, + 'email', CASE WHEN payload ? 'email' THEN payload->>'email' ELSE v_current.email END, + 'representante_legal', CASE WHEN payload ? 'representante_legal' THEN payload->>'representante_legal' ELSE v_current.representante_legal END, + 'giro', CASE WHEN payload ? 'giro' THEN payload->>'giro' ELSE v_current.giro END + ); + + v_profile_err := core._company_validate_profile(v_merged, false); + IF v_profile_err IS NOT NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_update: %s (id=%s)', v_profile_err, v_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'field', 'profile') + ); + END IF; + + v_profile := core._company_normalize_profile(v_merged, v_current.name); + IF core._company_trim_text(v_profile->>'name') = '' THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_update: nombre de empresa obligatorio (id=%s)', v_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'field', 'name') + ); + END IF; + + v_code := v_current.code; + IF payload ? 'code' THEN + v_code := core._company_norm_code(payload->>'code'); + v_code_err := core._company_validate_code(v_code); + IF v_code_err IS NOT NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_update: %s (id=%s, código=%s)', v_code_err, v_id, v_code), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'field', 'code', 'code', v_code) + ); + END IF; + SELECT id INTO v_clash_id FROM companies WHERE code = v_code AND id <> v_id; + IF FOUND THEN + RETURN core.rpc_err( + 'CONFLICT', + format('fn_company_update: ya existe otra empresa con el código %s (id=%s)', v_code, v_clash_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'code', v_code, 'conflict_id', v_clash_id) + ); + END IF; + END IF; + + v_status := v_current.status; + IF payload ? 'status' THEN + IF (payload->>'status') NOT IN ('activo', 'inactivo') THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_update: estatus debe ser activo o inactivo (recibido=%s, id=%s)', + payload->>'status', v_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'field', 'status', 'status', payload->>'status') + ); + END IF; + IF v_current.kind = 'principal' AND payload->>'status' = 'inactivo' THEN + RETURN core.rpc_err( + 'VALIDATION', + format('fn_company_update: la empresa principal id=%s no se puede inactivar', v_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'kind', v_current.kind) + ); + END IF; + v_status := payload->>'status'; + END IF; + + UPDATE companies SET + name = v_profile->>'name', + code = v_code, + status = v_status, + registro_patronal = v_profile->>'registro_patronal', + razon_social = v_profile->>'razon_social', + nombre_comercial = v_profile->>'nombre_comercial', + rfc = v_profile->>'rfc', + regimen_fiscal = v_profile->>'regimen_fiscal', + clase_riesgo = v_profile->>'clase_riesgo', + domicilio_fiscal = v_profile->>'domicilio_fiscal', + codigo_postal = v_profile->>'codigo_postal', + ciudad = v_profile->>'ciudad', + estado = v_profile->>'estado', + telefono = v_profile->>'telefono', + email = v_profile->>'email', + representante_legal = v_profile->>'representante_legal', + giro = v_profile->>'giro' + WHERE id = v_id; + + IF v_code <> v_current.code THEN + UPDATE workers SET hire_type = v_code WHERE company_id = v_id; + END IF; + + SELECT to_jsonb(c) INTO v_company FROM companies c WHERE c.id = v_id; + RETURN core.rpc_ok( + jsonb_build_object('company', v_company), + format('Empresa id=%s actualizada (%s)', v_id, v_code), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'code', v_code) + ); +EXCEPTION + WHEN unique_violation THEN + RETURN core.rpc_err( + 'CONFLICT', + format('fn_company_update: ya existe una empresa con el código %s (id=%s)', v_code, v_id), + jsonb_build_object('fn', 'fn_company_update', 'id', v_id, 'code', v_code) + ); + WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_company_update', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-008h-fn-companies-grants endDelimiter:; splitStatements:true +GRANT EXECUTE ON FUNCTION core._company_trim_text(text) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core._company_norm_code(text) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core._company_norm_rfc(text) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core._company_code_from_name(text) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core._company_validate_code(text) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core._company_validate_profile(jsonb, boolean) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core._company_normalize_profile(jsonb, text) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core.fn_company_list(jsonb) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core.fn_company_get(jsonb) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core.fn_company_get_by_code(jsonb) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core.fn_next_company_code(jsonb) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core.fn_company_create(jsonb) TO panels_core_app; +GRANT EXECUTE ON FUNCTION core.fn_company_update(jsonb) TO panels_core_app;