-- ============================================================================= -- AgenciaPsi — Functions — Financeiro -- ============================================================================= -- auto_create_financial_record_from_session, create_financial_record_for_session, -- create_therapist_payout, get_financial_report, get_financial_summary, -- list_financial_records, mark_as_paid, mark_payout_as_paid, -- seed_default_financial_categories, sync_overdue_financial_records, -- trg_fn_financial_records_auto_overdue, set_insurance/services_updated_at -- ============================================================================= CREATE FUNCTION public.auto_create_financial_record_from_session() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_price NUMERIC(10,2); v_services_total NUMERIC(10,2); v_already_billed BOOLEAN; BEGIN -- ── Guards de saída rápida ────────────────────────────────────────────── -- Só processa quando o status muda PARA 'realizado' IF NEW.status::TEXT <> 'realizado' THEN RETURN NEW; END IF; -- Só processa quando houve mudança real de status IF OLD.status IS NOT DISTINCT FROM NEW.status THEN RETURN NEW; END IF; -- Só sessões (não bloqueios, feriados, etc.) IF NEW.tipo::TEXT <> 'sessao' THEN RETURN NEW; END IF; -- Paciente obrigatório para vincular a cobrança IF NEW.patient_id IS NULL THEN RETURN NEW; END IF; -- Sessões de pacote têm cobrança gerenciada por billing_contract IF NEW.billing_contract_id IS NOT NULL THEN RETURN NEW; END IF; -- Idempotência: já existe financial_record para este evento? SELECT billed INTO v_already_billed FROM public.agenda_eventos WHERE id = NEW.id; IF v_already_billed = TRUE THEN -- Confirma no financial_records também (dupla verificação) IF EXISTS ( SELECT 1 FROM public.financial_records WHERE agenda_evento_id = NEW.id AND deleted_at IS NULL ) THEN RETURN NEW; END IF; END IF; -- ── Busca do preço ────────────────────────────────────────────────────── v_price := NULL; -- Prioridade 1: soma dos serviços da regra de recorrência IF NEW.recurrence_id IS NOT NULL THEN SELECT COALESCE(SUM(rrs.final_price), 0) INTO v_services_total FROM public.recurrence_rule_services rrs WHERE rrs.rule_id = NEW.recurrence_id; IF v_services_total > 0 THEN v_price := v_services_total; END IF; -- Prioridade 2: price direto da regra (fallback se sem serviços) IF v_price IS NULL OR v_price = 0 THEN SELECT price INTO v_price FROM public.recurrence_rules WHERE id = NEW.recurrence_id; END IF; END IF; -- Prioridade 3: price do próprio evento de agenda IF v_price IS NULL OR v_price = 0 THEN v_price := NEW.price; END IF; -- Sem preço → não criar registro (não é erro, apenas skip silencioso) IF v_price IS NULL OR v_price <= 0 THEN RETURN NEW; END IF; -- ── Criação do financial_record ───────────────────────────────────────── INSERT INTO public.financial_records ( owner_id, tenant_id, patient_id, agenda_evento_id, type, amount, discount_amount, final_amount, clinic_fee_pct, clinic_fee_amount, status, due_date -- payment_method: NULL até o momento do pagamento (mark_as_paid preenche) ) VALUES ( NEW.owner_id, NEW.tenant_id, NEW.patient_id, NEW.id, 'receita', v_price, 0, v_price, 0, -- clinic_fee_pct: sem campo de configuração global no schema atual. 0, -- clinic_fee_amount: calculado manualmente ou via update posterior. 'pending', (NEW.inicio_em::DATE + 7) -- vencimento padrão: 7 dias após a sessão ); -- ── Marca sessão como billed ──────────────────────────────────────────── -- UPDATE em billed (não em status) → não re-dispara este trigger UPDATE public.agenda_eventos SET billed = TRUE WHERE id = NEW.id; RETURN NEW; EXCEPTION WHEN OTHERS THEN -- Log silencioso: nunca bloquear a agenda por falha financeira RAISE WARNING '[auto_create_financial_record_from_session] evento=% erro=%', NEW.id, SQLERRM; RETURN NEW; END; $$; ALTER FUNCTION public.auto_create_financial_record_from_session() OWNER TO supabase_admin; -- -- Name: FUNCTION auto_create_financial_record_from_session(); Type: COMMENT; Schema: public; Owner: supabase_admin -- COMMENT ON FUNCTION public.auto_create_financial_record_from_session() IS 'Trigger que cria automaticamente um financial_record (receita, pending) quando uma sessão de agenda é marcada como realizada. Prioridade de preço: recurrence_rule_services > recurrence_rules.price > agenda_eventos.price. Skip silencioso se sem preço, pacote ou registro já existente.'; -- -- Name: can_delete_patient(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.can_delete_patient(p_patient_id uuid) RETURNS boolean CREATE FUNCTION public.create_financial_record_for_session(p_tenant_id uuid, p_owner_id uuid, p_patient_id uuid, p_agenda_evento_id uuid, p_amount numeric, p_due_date date) RETURNS SETOF public.financial_records LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_existing public.financial_records%ROWTYPE; v_new public.financial_records%ROWTYPE; BEGIN -- Idempotência: retorna o registro existente se já foi criado SELECT * INTO v_existing FROM public.financial_records WHERE agenda_evento_id = p_agenda_evento_id AND deleted_at IS NULL LIMIT 1; IF FOUND THEN RETURN NEXT v_existing; RETURN; END IF; -- Cria o novo registro INSERT INTO public.financial_records ( tenant_id, owner_id, patient_id, agenda_evento_id, amount, discount_amount, final_amount, status, due_date ) VALUES ( p_tenant_id, p_owner_id, p_patient_id, p_agenda_evento_id, p_amount, 0, p_amount, 'pending', p_due_date ) RETURNING * INTO v_new; -- Marca o evento da agenda como billed = true UPDATE public.agenda_eventos SET billed = TRUE WHERE id = p_agenda_evento_id; RETURN NEXT v_new; END; $$; ALTER FUNCTION public.create_financial_record_for_session(p_tenant_id uuid, p_owner_id uuid, p_patient_id uuid, p_agenda_evento_id uuid, p_amount numeric, p_due_date date) OWNER TO supabase_admin; -- -- Name: create_patient_intake_request(text, text, text, text, text, boolean); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.create_patient_intake_request(p_token text, p_name text, p_email text DEFAULT NULL::text, p_phone text DEFAULT NULL::text, p_notes text DEFAULT NULL::text, p_consent boolean DEFAULT false) RETURNS uuid CREATE FUNCTION public.create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date) RETURNS public.therapist_payouts LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_payout public.therapist_payouts%ROWTYPE; v_total_sessions INTEGER; v_gross NUMERIC(10,2); v_clinic_fee NUMERIC(10,2); v_net NUMERIC(10,2); BEGIN -- ── Verificação de permissão ──────────────────────────────────────────── -- Apenas o próprio terapeuta ou o tenant_admin pode criar o repasse IF auth.uid() <> p_therapist_id AND NOT public.is_tenant_admin(p_tenant_id) THEN RAISE EXCEPTION 'Sem permissão para criar repasse para este terapeuta.'; END IF; -- ── Verifica se já existe repasse para o mesmo período ───────────────── IF EXISTS ( SELECT 1 FROM public.therapist_payouts WHERE owner_id = p_therapist_id AND tenant_id = p_tenant_id AND period_start = p_period_start AND period_end = p_period_end AND status <> 'cancelled' ) THEN RAISE EXCEPTION 'Já existe um repasse ativo para o período % a % deste terapeuta.', p_period_start, p_period_end; END IF; -- ── Agrega os financial_records elegíveis ────────────────────────────── -- Elegíveis: paid, receita, owner=terapeuta, tenant correto, paid_at no período, -- não soft-deleted, ainda não vinculados a nenhum payout. SELECT COUNT(*) AS total_sessions, COALESCE(SUM(amount), 0) AS gross_amount, COALESCE(SUM(clinic_fee_amount), 0) AS clinic_fee_total, COALESCE(SUM(net_amount), 0) AS net_amount INTO v_total_sessions, v_gross, v_clinic_fee, v_net FROM public.financial_records fr WHERE fr.owner_id = p_therapist_id AND fr.tenant_id = p_tenant_id AND fr.type = 'receita' AND fr.status = 'paid' AND fr.deleted_at IS NULL AND fr.paid_at::DATE BETWEEN p_period_start AND p_period_end AND NOT EXISTS ( SELECT 1 FROM public.therapist_payout_records tpr WHERE tpr.financial_record_id = fr.id ); -- Sem registros elegíveis → não criar payout vazio IF v_total_sessions = 0 THEN RAISE EXCEPTION 'Nenhum registro financeiro elegível encontrado para o período % a %.', p_period_start, p_period_end; END IF; -- ── Cria o repasse ───────────────────────────────────────────────────── INSERT INTO public.therapist_payouts ( owner_id, tenant_id, period_start, period_end, total_sessions, gross_amount, clinic_fee_total, net_amount, status ) VALUES ( p_therapist_id, p_tenant_id, p_period_start, p_period_end, v_total_sessions, v_gross, v_clinic_fee, v_net, 'pending' ) RETURNING * INTO v_payout; -- ── Vincula os financial_records ao repasse ──────────────────────────── INSERT INTO public.therapist_payout_records (payout_id, financial_record_id) SELECT v_payout.id, fr.id FROM public.financial_records fr WHERE fr.owner_id = p_therapist_id AND fr.tenant_id = p_tenant_id AND fr.type = 'receita' AND fr.status = 'paid' AND fr.deleted_at IS NULL AND fr.paid_at::DATE BETWEEN p_period_start AND p_period_end AND NOT EXISTS ( SELECT 1 FROM public.therapist_payout_records tpr WHERE tpr.financial_record_id = fr.id ); RETURN v_payout; END; $$; ALTER FUNCTION public.create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date) OWNER TO supabase_admin; -- -- Name: FUNCTION create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date); Type: COMMENT; Schema: public; Owner: supabase_admin -- COMMENT ON FUNCTION public.create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date) IS 'Cria um repasse para o terapeuta com todos os financial_records paid+receita do período que ainda não estejam vinculados a outro repasse. Lança exceção se não houver registros elegíveis ou se já houver repasse ativo no período.'; -- -- Name: current_member_id(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.current_member_id(p_tenant_id uuid) RETURNS uuid CREATE FUNCTION public.get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text DEFAULT 'month'::text) 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 sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$ -- ── Valida p_group_by antes de executar ────────────────────────────────── -- (lança erro se valor inválido; plpgsql seria necessário para isso em SQL puro, -- então usamos um CTE de validação com CASE WHEN para retornar vazio em vez de erro) WITH base AS ( SELECT fr.type, fr.amount, fr.final_amount, fr.status, fr.deleted_at, -- Chave de agrupamento calculada conforme p_group_by 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 -- group_by inválido → group_key NULL → retorno vazio END AS gkey, -- Label legível (enriquecido via JOIN abaixo quando possível) 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 public.financial_records fr LEFT JOIN public.financial_categories fc ON fc.id = fr.category_id LEFT JOIN public.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 AS group_key, glabel AS group_label, COALESCE(SUM(final_amount) FILTER (WHERE type = 'receita' AND status = 'paid'), 0) AS total_receitas, COALESCE(SUM(final_amount) FILTER (WHERE type = 'despesa' AND status = 'paid'), 0) AS total_despesas, COALESCE(SUM(final_amount) FILTER (WHERE type = 'receita' AND status = 'paid'), 0) - COALESCE(SUM(final_amount) FILTER (WHERE type = 'despesa' AND status = 'paid'), 0) AS saldo, COALESCE(SUM(final_amount) FILTER (WHERE status = 'pending'), 0) AS total_pendente, COALESCE(SUM(final_amount) FILTER (WHERE status = 'overdue'), 0) AS total_overdue, COUNT(*) AS count_records FROM base WHERE gkey IS NOT NULL -- descarta p_group_by inválido GROUP BY gkey, glabel ORDER BY gkey ASC; $$; ALTER FUNCTION public.get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text) OWNER TO supabase_admin; -- -- Name: FUNCTION get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text); Type: COMMENT; Schema: public; Owner: supabase_admin -- COMMENT ON FUNCTION public.get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text) IS 'Relatório financeiro agrupado por mês, semana ISO, categoria ou paciente. p_group_by aceita: ''month'' | ''week'' | ''category'' | ''patient''. Totais de receita/despesa consideram apenas registros com status=paid. total_pendente e total_overdue incluem todos os tipos (receita + despesa).'; -- -- Name: get_financial_summary(uuid, integer, integer); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.get_financial_summary(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 sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$ SELECT -- Receitas pagas no período COALESCE(SUM(amount) FILTER ( WHERE type = 'receita' AND status = 'paid' ), 0) AS total_receitas, -- Despesas pagas no período COALESCE(SUM(amount) FILTER ( WHERE type = 'despesa' AND status = 'paid' ), 0) AS total_despesas, -- Tudo pendente ou vencido (receitas + despesas) COALESCE(SUM(amount) FILTER ( WHERE status IN ('pending', 'overdue') ), 0) AS total_pendente, -- Saldo líquido (receitas pagas − despesas pagas) COALESCE(SUM(amount) FILTER ( WHERE type = 'receita' AND status = 'paid' ), 0) - COALESCE(SUM(amount) FILTER ( WHERE type = 'despesa' AND status = 'paid' ), 0) AS saldo_liquido, -- Total repassado à clínica (apenas receitas pagas) COALESCE(SUM(clinic_fee_amount) FILTER ( WHERE type = 'receita' AND status = 'paid' ), 0) AS total_repasse, -- Contadores (excluindo soft-deleted) COUNT(*) FILTER (WHERE type = 'receita' AND deleted_at IS NULL) AS count_receitas, COUNT(*) FILTER (WHERE type = 'despesa' AND deleted_at IS NULL) AS count_despesas FROM public.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; $$; ALTER FUNCTION public.get_financial_summary(p_owner_id uuid, p_year integer, p_month integer) OWNER TO supabase_admin; -- -- Name: get_my_email(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.get_my_email() RETURNS text CREATE FUNCTION public.list_financial_records(p_owner_id uuid, p_year integer DEFAULT NULL::integer, p_month integer DEFAULT NULL::integer, p_type text DEFAULT NULL::text, p_status text DEFAULT NULL::text, p_patient_id uuid DEFAULT NULL::uuid, p_limit integer DEFAULT 50, p_offset integer DEFAULT 0) RETURNS SETOF public.financial_records LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$ SELECT * FROM public.financial_records WHERE owner_id = p_owner_id AND deleted_at IS NULL AND (p_type IS NULL OR type::TEXT = p_type) AND (p_status IS NULL OR status = p_status) AND (p_patient_id IS NULL OR patient_id = p_patient_id) AND (p_year IS NULL OR EXTRACT(YEAR FROM COALESCE(paid_at::DATE, due_date, created_at::DATE)) = p_year) AND (p_month IS NULL OR EXTRACT(MONTH FROM COALESCE(paid_at::DATE, due_date, created_at::DATE)) = p_month) ORDER BY COALESCE(paid_at, due_date::TIMESTAMPTZ, created_at) DESC LIMIT p_limit OFFSET p_offset; $$; ALTER FUNCTION public.list_financial_records(p_owner_id uuid, p_year integer, p_month integer, p_type text, p_status text, p_patient_id uuid, p_limit integer, p_offset integer) OWNER TO supabase_admin; -- -- Name: mark_as_paid(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.mark_as_paid(p_financial_record_id uuid, p_payment_method text) RETURNS SETOF public.financial_records LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_record public.financial_records%ROWTYPE; BEGIN -- Garante que o registro pertence ao usuário autenticado (RLS não aplica em SECURITY DEFINER) SELECT * INTO v_record FROM public.financial_records WHERE id = p_financial_record_id AND owner_id = auth.uid() AND deleted_at IS NULL; IF NOT FOUND THEN RAISE EXCEPTION 'Registro financeiro não encontrado ou sem permissão.'; END IF; IF v_record.status NOT IN ('pending', 'overdue') THEN RAISE EXCEPTION 'Apenas cobranças pendentes ou vencidas podem ser marcadas como pagas.'; END IF; UPDATE public.financial_records SET status = 'paid', paid_at = NOW(), payment_method = p_payment_method, updated_at = NOW() WHERE id = p_financial_record_id RETURNING * INTO v_record; RETURN NEXT v_record; END; $$; ALTER FUNCTION public.mark_as_paid(p_financial_record_id uuid, p_payment_method text) OWNER TO supabase_admin; -- -- Name: mark_payout_as_paid(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.mark_payout_as_paid(p_payout_id uuid) RETURNS public.therapist_payouts CREATE FUNCTION public.mark_payout_as_paid(p_payout_id uuid) RETURNS public.therapist_payouts LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_payout public.therapist_payouts%ROWTYPE; BEGIN -- Busca o payout SELECT * INTO v_payout FROM public.therapist_payouts WHERE id = p_payout_id; IF NOT FOUND THEN RAISE EXCEPTION 'Repasse não encontrado: %', p_payout_id; END IF; -- Verifica permissão: apenas tenant_admin do tenant do repasse IF NOT public.is_tenant_admin(v_payout.tenant_id) THEN RAISE EXCEPTION 'Apenas o administrador da clínica pode marcar repasses como pagos.'; END IF; -- Verifica status IF v_payout.status <> 'pending' THEN RAISE EXCEPTION 'Repasse já está com status ''%''. Apenas repasses pendentes podem ser pagos.', v_payout.status; END IF; -- Atualiza UPDATE public.therapist_payouts SET status = 'paid', paid_at = NOW(), updated_at = NOW() WHERE id = p_payout_id RETURNING * INTO v_payout; RETURN v_payout; END; $$; ALTER FUNCTION public.mark_payout_as_paid(p_payout_id uuid) OWNER TO supabase_admin; -- -- Name: FUNCTION mark_payout_as_paid(p_payout_id uuid); Type: COMMENT; Schema: public; Owner: supabase_admin -- COMMENT ON FUNCTION public.mark_payout_as_paid(p_payout_id uuid) IS 'Marca um repasse de terapeuta como pago. Apenas o tenant_admin pode chamar. Apenas repasses com status=pending podem ser finalizados.'; -- -- Name: my_tenants(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.my_tenants() RETURNS TABLE(tenant_id uuid, role text, status text, kind text) CREATE FUNCTION public.seed_default_financial_categories(p_user_id uuid) RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ BEGIN INSERT INTO public.financial_categories (user_id, name, type, color, icon, sort_order) VALUES (p_user_id, 'Sessão', 'receita', '#22c55e', 'pi pi-heart', 1), (p_user_id, 'Supervisão', 'receita', '#6366f1', 'pi pi-users', 2), (p_user_id, 'Convênio', 'receita', '#3b82f6', 'pi pi-building', 3), (p_user_id, 'Grupo terapêutico', 'receita', '#f59e0b', 'pi pi-sitemap', 4), (p_user_id, 'Outro (receita)', 'receita', '#8b5cf6', 'pi pi-plus-circle', 5), (p_user_id, 'Aluguel sala', 'despesa', '#ef4444', 'pi pi-home', 1), (p_user_id, 'Plataforma/SaaS', 'despesa', '#f97316', 'pi pi-desktop', 2), (p_user_id, 'Repasse clínica', 'despesa', '#64748b', 'pi pi-arrow-right-arrow-left', 3), (p_user_id, 'Supervisão (custo)', 'despesa', '#6366f1', 'pi pi-users', 4), (p_user_id, 'Outro (despesa)', 'despesa', '#94a3b8', 'pi pi-minus-circle', 5) ON CONFLICT DO NOTHING; END; $$; ALTER FUNCTION public.seed_default_financial_categories(p_user_id uuid) OWNER TO supabase_admin; -- -- Name: seed_determined_commitments(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.seed_determined_commitments(p_tenant_id uuid) RETURNS void CREATE FUNCTION public.set_insurance_plans_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$; ALTER FUNCTION public.set_insurance_plans_updated_at() OWNER TO supabase_admin; -- -- Name: set_owner_id(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.set_owner_id() RETURNS trigger LANGUAGE plpgsql AS $$ begin if new.owner_id is null then new.owner_id := auth.uid(); end if; return new; end; $$; ALTER FUNCTION public.set_owner_id() OWNER TO supabase_admin; -- -- Name: set_services_updated_at(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.set_services_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$; ALTER FUNCTION public.set_services_updated_at() OWNER TO supabase_admin; -- -- Name: set_tenant_feature_exception(uuid, text, boolean, text); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.set_tenant_feature_exception(p_tenant_id uuid, p_feature_key text, p_enabled boolean, p_reason text DEFAULT NULL::text) RETURNS void CREATE FUNCTION public.set_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$; ALTER FUNCTION public.set_updated_at() OWNER TO supabase_admin; -- -- Name: set_updated_at_recurrence(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.set_updated_at_recurrence() RETURNS trigger CREATE FUNCTION public.sync_overdue_financial_records() RETURNS integer LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_count integer; BEGIN UPDATE public.financial_records SET status = 'overdue', updated_at = NOW() WHERE status = 'pending' AND due_date IS NOT NULL AND due_date < CURRENT_DATE AND deleted_at IS NULL; GET DIAGNOSTICS v_count = ROW_COUNT; RETURN v_count; END; $$; ALTER FUNCTION public.sync_overdue_financial_records() OWNER TO supabase_admin; -- -- Name: FUNCTION sync_overdue_financial_records(); Type: COMMENT; Schema: public; Owner: supabase_admin -- COMMENT ON FUNCTION public.sync_overdue_financial_records() IS 'Marca como overdue todos os financial_records pendentes com due_date vencido. Pode ser chamada manualmente, via pg_cron ou via Supabase Edge Function agendada.'; -- -- Name: tenant_accept_invite(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.tenant_accept_invite(p_token uuid) RETURNS jsonb CREATE FUNCTION public.trg_fn_financial_records_auto_overdue() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ BEGIN IF NEW.status = 'pending' AND NEW.due_date IS NOT NULL AND NEW.due_date < CURRENT_DATE THEN NEW.status := 'overdue'; END IF; RETURN NEW; END; $$; ALTER FUNCTION public.trg_fn_financial_records_auto_overdue() OWNER TO supabase_admin; -- -- Name: unstick_notification_queue(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.unstick_notification_queue() RETURNS integer LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_unstuck integer; BEGIN UPDATE public.notification_queue SET status = 'pendente', attempts = attempts + 1, last_error = 'Timeout: preso em processando por >10min', next_retry_at = now() + interval '2 minutes' WHERE status = 'processando' AND updated_at < now() - interval '10 minutes'; GET DIAGNOSTICS v_unstuck = ROW_COUNT; RETURN v_unstuck; END; $$; ALTER FUNCTION public.unstick_notification_queue() OWNER TO supabase_admin; -- -- Name: update_payment_settings_updated_at(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.update_payment_settings_updated_at() RETURNS trigger CREATE FUNCTION public.update_payment_settings_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$; ALTER FUNCTION public.update_payment_settings_updated_at() OWNER TO supabase_admin; -- -- Name: update_professional_pricing_updated_at(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.update_professional_pricing_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$; ALTER FUNCTION public.update_professional_pricing_updated_at() OWNER TO supabase_admin; -- -- Name: user_has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.user_has_feature(_user_id uuid, _feature text) RETURNS boolean