--liquibase formatted sql -- PANELS · core · fix needs_badge cast en fn_worker_create/update --changeset panel:core-016a-fn-worker-needs-badge splitStatements:false CREATE OR REPLACE FUNCTION core.fn_worker_create(payload jsonb) RETURNS jsonb LANGUAGE plpgsql SECURITY INVOKER SET search_path = core AS $$ DECLARE v_tid integer := COALESCE( NULLIF(payload->>'tenant_id', '')::integer, NULLIF(current_setting('app.tenant_id', true), '')::integer ); v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint; v_errors jsonb; v_norm jsonb; v_existing workers%ROWTYPE; v_row workers%ROWTYPE; v_project_status text; v_refresh jsonb; BEGIN v_errors := core._worker_validate_fields(payload); IF v_errors <> '{}'::jsonb THEN RETURN core.rpc_err( 'VALIDATION', 'fn_worker_create: datos del trabajador incompletos o inválidos', jsonb_build_object('fn', 'fn_worker_create', 'field', 'payload'), v_errors ); END IF; v_norm := core._worker_normalize(payload); SELECT * INTO v_existing FROM workers WHERE curp = v_norm->>'curp' OR rfc = v_norm->>'rfc' OR nss = v_norm->>'nss' LIMIT 1; IF FOUND THEN RETURN core.rpc_err( 'CONFLICT', core._worker_conflict_message(v_existing, v_norm->>'curp', v_norm->>'rfc', v_norm->>'nss'), jsonb_build_object('fn', 'fn_worker_create', 'worker_id', v_existing.id) ); END IF; IF v_project_id IS NOT NULL THEN SELECT status INTO v_project_status FROM projects WHERE id = v_project_id; IF NOT FOUND THEN RETURN core.rpc_err( 'NOT_FOUND', format('fn_worker_create: proyecto id=%s no encontrado', v_project_id), jsonb_build_object('fn', 'fn_worker_create', 'project_id', v_project_id) ); END IF; IF v_project_status <> 'activo' THEN RETURN core.rpc_err( 'VALIDATION', 'fn_worker_create: solo se asigna personal a proyectos activos', jsonb_build_object('fn', 'fn_worker_create', 'project_id', v_project_id, 'status', v_project_status) ); END IF; END IF; INSERT INTO workers ( first_name, middle_name, last_name_p, last_name_m, curp, rfc, nss, phone, email, address, blood_type, hire_type, company_id, position, risk_code, work_type, daily_wage, needs_badge, status, tenant_id ) VALUES ( v_norm->>'first_name', v_norm->>'middle_name', v_norm->>'last_name_p', v_norm->>'last_name_m', v_norm->>'curp', v_norm->>'rfc', v_norm->>'nss', v_norm->>'phone', v_norm->>'email', v_norm->>'address', v_norm->>'blood_type', '', NULL, v_norm->>'position', v_norm->>'risk_code', v_norm->>'work_type', (v_norm->>'daily_wage')::numeric(12,2), COALESCE((v_norm->'needs_badge')::boolean, true), v_norm->>'status', v_tid ) RETURNING * INTO v_row; IF v_project_id IS NOT NULL THEN INSERT INTO assignments (worker_id, project_id, active, start_date) VALUES (v_row.id, v_project_id, true, current_date) ON CONFLICT (worker_id, project_id) DO UPDATE SET active = true, start_date = CASE WHEN assignments.active = false THEN excluded.start_date ELSE assignments.start_date END, end_date = NULL; END IF; v_refresh := core.fn_worker_refresh_pipeline(jsonb_build_object('worker_id', v_row.id, 'tenant_id', v_tid)); RETURN core.rpc_created( jsonb_build_object('worker', to_jsonb(v_row), 'pipeline', v_refresh->'data'), format('Trabajador %s %s creado (id=%s)', v_row.first_name, v_row.last_name_p, v_row.id), jsonb_build_object('fn', 'fn_worker_create', 'id', v_row.id) ); EXCEPTION WHEN OTHERS THEN RETURN core.rpc_from_exception('fn_worker_create', SQLSTATE, SQLERRM); END; $$; CREATE OR REPLACE FUNCTION core.fn_worker_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 := COALESCE( NULLIF(payload->>'tenant_id', '')::integer, NULLIF(current_setting('app.tenant_id', true), '')::integer ); v_cur workers%ROWTYPE; v_merged jsonb; v_errors jsonb; v_norm jsonb; v_existing workers%ROWTYPE; v_refresh jsonb; BEGIN IF v_id IS NULL THEN RETURN core.rpc_err( 'VALIDATION', 'fn_worker_update: id es obligatorio para actualizar un trabajador', jsonb_build_object('fn', 'fn_worker_update', 'field', 'id') ); END IF; SELECT * INTO v_cur FROM workers WHERE id = v_id; IF NOT FOUND THEN RETURN core.rpc_err( 'NOT_FOUND', format('fn_worker_update: trabajador id=%s no encontrado', v_id), jsonb_build_object('fn', 'fn_worker_update', 'id', v_id) ); END IF; v_merged := to_jsonb(v_cur) || payload; v_errors := core._worker_validate_fields(v_merged); IF v_errors <> '{}'::jsonb THEN RETURN core.rpc_err( 'VALIDATION', format('fn_worker_update: datos del trabajador inválidos (id=%s)', v_id), jsonb_build_object('fn', 'fn_worker_update', 'id', v_id), v_errors ); END IF; v_norm := core._worker_normalize(v_merged); SELECT * INTO v_existing FROM workers WHERE (curp = v_norm->>'curp' OR rfc = v_norm->>'rfc' OR nss = v_norm->>'nss') AND id <> v_id LIMIT 1; IF FOUND THEN RETURN core.rpc_err( 'CONFLICT', core._worker_conflict_message(v_existing, v_norm->>'curp', v_norm->>'rfc', v_norm->>'nss'), jsonb_build_object('fn', 'fn_worker_update', 'id', v_id, 'worker_id', v_existing.id) ); END IF; UPDATE workers SET first_name = v_norm->>'first_name', middle_name = v_norm->>'middle_name', last_name_p = v_norm->>'last_name_p', last_name_m = v_norm->>'last_name_m', curp = v_norm->>'curp', rfc = v_norm->>'rfc', nss = v_norm->>'nss', phone = v_norm->>'phone', email = v_norm->>'email', address = v_norm->>'address', blood_type = v_norm->>'blood_type', hire_type = v_cur.hire_type, company_id = v_cur.company_id, position = v_norm->>'position', risk_code = v_norm->>'risk_code', work_type = v_norm->>'work_type', daily_wage = (v_norm->>'daily_wage')::numeric(12,2), needs_badge = COALESCE((v_norm->'needs_badge')::boolean, v_cur.needs_badge, true), status = v_norm->>'status' WHERE id = v_id RETURNING * INTO v_cur; v_refresh := core.fn_worker_refresh_pipeline(jsonb_build_object('worker_id', v_id, 'tenant_id', v_tid)); RETURN core.rpc_ok( jsonb_build_object('worker', to_jsonb(v_cur), 'pipeline', v_refresh->'data'), format('Trabajador %s %s actualizado (id=%s)', v_cur.first_name, v_cur.last_name_p, v_id), jsonb_build_object('fn', 'fn_worker_update', 'id', v_id) ); EXCEPTION WHEN OTHERS THEN RETURN core.rpc_from_exception('fn_worker_update', SQLSTATE, SQLERRM); END; $$;