Files
agenciapsilmno/database-novo/manual/f6_2g_sql_to_plpgsql.supabase_admin.sql
T
Leonardo ee82985dc3 F6.2 Lote G: funcoes SQL puras -> plpgsql + roteamento (completa F6.2)
DB (supabase_admin, manual/f6_2g_sql_to_plpgsql.supabase_admin.sql): SQL puro
nao permite set_config dinamico -> converte 5 pra plpgsql + p_tenant_id +
_tenant_route:
- get_financial_summary, get_financial_report, get_patient_session_counts
  (remove filtro tenant_id IN, schema-scoped)
- list_financial_records: RETURNS SETOF financial_records -> jsonb (array,
  transparente no FE)
- get_entity_primary_phone (interno, 0 callers): herda search_path do chamador
  (sem SET, unqualified)
Smoke: get_financial_summary + list_financial_records (array 5) OK.

Frontend (3 arquivos): p_tenant_id de activeTenantId/resolveTenantId nas
chamadas de get_financial_summary/list_financial_records/get_patient_session_
counts. Build passa.

=== F6.2 COMPLETA: 66 funcoes migradas (triggers A/B/C + RPCs D/E/F/G) ===

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:00:16 -03:00

127 lines
7.5 KiB
PL/PgSQL

-- =============================================================================
-- F6.2 Lote G — funções SQL puras → plpgsql + roteamento por tenant
--
-- ⚠️ APLICAR COMO supabase_admin.
--
-- SQL puro não permite set_config dinâmico do search_path (limitação 3 do
-- blueprint) → converter pra plpgsql. Adicionam p_tenant_id + _tenant_route.
-- RETURNS SETOF <tabela_tenant> → jsonb. get_entity_primary_phone (interno,
-- 0 callers) herda search_path do chamador (sem SET, unqualified).
-- =============================================================================
BEGIN;
DROP FUNCTION IF EXISTS public.get_financial_summary(uuid, integer, integer);
DROP FUNCTION IF EXISTS public.get_financial_summary(uuid, uuid, integer, integer);
CREATE FUNCTION public.get_financial_summary(p_tenant_id uuid, p_owner_id uuid, p_year integer, p_month integer)
RETURNS TABLE(total_receitas numeric, total_despesas numeric, total_pendente numeric, saldo_liquido numeric, total_repasse numeric, count_receitas bigint, count_despesas bigint)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
BEGIN
PERFORM set_config('search_path', public._tenant_route(p_tenant_id) || ',public,pg_temp', true);
RETURN QUERY
SELECT
COALESCE(SUM(amount) FILTER (WHERE type='receita' AND status='paid'), 0),
COALESCE(SUM(amount) FILTER (WHERE type='despesa' AND status='paid'), 0),
COALESCE(SUM(amount) FILTER (WHERE status IN ('pending','overdue')), 0),
COALESCE(SUM(amount) FILTER (WHERE type='receita' AND status='paid'), 0) - COALESCE(SUM(amount) FILTER (WHERE type='despesa' AND status='paid'), 0),
COALESCE(SUM(clinic_fee_amount) FILTER (WHERE type='receita' AND status='paid'), 0),
COUNT(*) FILTER (WHERE type='receita' AND deleted_at IS NULL),
COUNT(*) FILTER (WHERE type='despesa' AND deleted_at IS NULL)
FROM financial_records
WHERE owner_id = p_owner_id AND deleted_at IS NULL
AND EXTRACT(YEAR FROM COALESCE(paid_at::date, due_date, created_at::date)) = p_year
AND EXTRACT(MONTH FROM COALESCE(paid_at::date, due_date, created_at::date)) = p_month;
END $$;
-- list_financial_records: RETURNS SETOF financial_records → jsonb (array)
DROP FUNCTION IF EXISTS public.list_financial_records(uuid, integer, integer, text, text, uuid, integer, integer);
DROP FUNCTION IF EXISTS public.list_financial_records(uuid, uuid, integer, integer, text, text, uuid, integer, integer);
CREATE FUNCTION public.list_financial_records(p_tenant_id uuid, p_owner_id uuid, p_year integer DEFAULT NULL, p_month integer DEFAULT NULL, p_type text DEFAULT NULL, p_status text DEFAULT NULL, p_patient_id uuid DEFAULT NULL, p_limit integer DEFAULT 50, p_offset integer DEFAULT 0)
RETURNS jsonb LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
DECLARE v_result jsonb;
BEGIN
PERFORM set_config('search_path', public._tenant_route(p_tenant_id) || ',public,pg_temp', true);
SELECT COALESCE(jsonb_agg(row_json), '[]'::jsonb) INTO v_result FROM (
SELECT to_jsonb(fr) AS row_json
FROM financial_records fr
WHERE fr.owner_id = p_owner_id AND fr.deleted_at IS NULL
AND (p_type IS NULL OR fr.type::text = p_type)
AND (p_status IS NULL OR fr.status = p_status)
AND (p_patient_id IS NULL OR fr.patient_id = p_patient_id)
AND (p_year IS NULL OR EXTRACT(YEAR FROM COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date)) = p_year)
AND (p_month IS NULL OR EXTRACT(MONTH FROM COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date)) = p_month)
ORDER BY COALESCE(fr.paid_at, fr.due_date::timestamptz, fr.created_at) DESC
LIMIT p_limit OFFSET p_offset
) sub;
RETURN v_result;
END $$;
DROP FUNCTION IF EXISTS public.get_patient_session_counts(uuid[]);
DROP FUNCTION IF EXISTS public.get_patient_session_counts(uuid, uuid[]);
CREATE FUNCTION public.get_patient_session_counts(p_tenant_id uuid, p_patient_ids uuid[])
RETURNS TABLE(patient_id uuid, session_count integer, last_session_at timestamptz)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
BEGIN
PERFORM set_config('search_path', public._tenant_route(p_tenant_id) || ',public,pg_temp', true);
RETURN QUERY
SELECT ae.patient_id, COUNT(*)::int, MAX(ae.inicio_em)
FROM agenda_eventos ae
WHERE ae.patient_id = ANY(p_patient_ids)
GROUP BY ae.patient_id;
END $$;
DROP FUNCTION IF EXISTS public.get_financial_report(uuid, date, date, text);
DROP FUNCTION IF EXISTS public.get_financial_report(uuid, uuid, date, date, text);
CREATE FUNCTION public.get_financial_report(p_tenant_id uuid, p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text DEFAULT 'month')
RETURNS TABLE(group_key text, group_label text, total_receitas numeric, total_despesas numeric, saldo numeric, total_pendente numeric, total_overdue numeric, count_records bigint)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
BEGIN
PERFORM set_config('search_path', public._tenant_route(p_tenant_id) || ',public,pg_temp', true);
RETURN QUERY
WITH base AS (
SELECT fr.type, fr.amount, fr.final_amount, fr.status, fr.deleted_at,
CASE p_group_by
WHEN 'month' THEN TO_CHAR(COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date),'YYYY-MM')
WHEN 'week' THEN TO_CHAR(COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date),'IYYY-"W"IW')
WHEN 'category' THEN COALESCE(fr.category_id::text, fr.category, 'sem_categoria')
WHEN 'patient' THEN COALESCE(fr.patient_id::text, 'sem_paciente')
ELSE NULL END AS gkey,
CASE p_group_by
WHEN 'month' THEN TO_CHAR(COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date),'YYYY-MM')
WHEN 'week' THEN TO_CHAR(COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date),'IYYY-"W"IW')
WHEN 'category' THEN COALESCE(fc.name, fr.category, 'Sem categoria')
WHEN 'patient' THEN COALESCE(p.nome_completo, fr.patient_id::text, 'Sem paciente')
ELSE NULL END AS glabel
FROM financial_records fr
LEFT JOIN financial_categories fc ON fc.id = fr.category_id
LEFT JOIN patients p ON p.id = fr.patient_id
WHERE fr.owner_id = p_owner_id AND fr.deleted_at IS NULL
AND COALESCE(fr.paid_at::date, fr.due_date, fr.created_at::date) BETWEEN p_start_date AND p_end_date
)
SELECT gkey, glabel,
COALESCE(SUM(final_amount) FILTER (WHERE type='receita' AND status='paid'),0),
COALESCE(SUM(final_amount) FILTER (WHERE type='despesa' AND status='paid'),0),
COALESCE(SUM(final_amount) FILTER (WHERE type='receita' AND status='paid'),0) - COALESCE(SUM(final_amount) FILTER (WHERE type='despesa' AND status='paid'),0),
COALESCE(SUM(final_amount) FILTER (WHERE status='pending'),0),
COALESCE(SUM(final_amount) FILTER (WHERE status='overdue'),0),
COUNT(*)
FROM base WHERE gkey IS NOT NULL GROUP BY gkey, glabel ORDER BY gkey ASC;
END $$;
-- get_entity_primary_phone: interno (0 callers). Sem SET search_path → herda do
-- chamador (que roteia pro schema). Unqualified. Mantém assinatura.
CREATE OR REPLACE FUNCTION public.get_entity_primary_phone(p_entity_type text, p_entity_id uuid)
RETURNS text LANGUAGE sql STABLE SECURITY DEFINER
AS $$
SELECT number FROM contact_phones
WHERE entity_type = p_entity_type AND entity_id = p_entity_id
ORDER BY is_primary DESC, position ASC, created_at ASC
LIMIT 1;
$$;
COMMIT;