-- =============================================================================
-- 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
