Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
818
database-novo/schema/03_functions/financial.sql
Normal file
818
database-novo/schema/03_functions/financial.sql
Normal file
@@ -0,0 +1,818 @@
|
||||
-- =============================================================================
|
||||
-- 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
|
||||
Reference in New Issue
Block a user