-- ============================================================================= -- AgenciaPsi — Functions — Pacientes -- ============================================================================= -- can_delete_patient, safe_delete_patient, create_patient_intake_request, -- create_patient_intake_request_v2, rotate_patient_invite_token, -- patients_validate_member_consistency, prevent_system_group_changes, -- seed_default_patient_groups -- ============================================================================= CREATE FUNCTION public.can_delete_patient(p_patient_id uuid) RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT NOT EXISTS ( SELECT 1 FROM public.agenda_eventos WHERE patient_id = p_patient_id UNION ALL SELECT 1 FROM public.recurrence_rules WHERE patient_id = p_patient_id UNION ALL SELECT 1 FROM public.billing_contracts WHERE patient_id = p_patient_id ); $$; ALTER FUNCTION public.can_delete_patient(p_patient_id uuid) OWNER TO supabase_admin; -- -- Name: cancel_notifications_on_opt_out(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.cancel_notifications_on_opt_out() RETURNS trigger 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 LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ declare v_owner uuid; v_active boolean; v_expires timestamptz; v_max_uses int; v_uses int; v_id uuid; begin select owner_id, active, expires_at, max_uses, uses into v_owner, v_active, v_expires, v_max_uses, v_uses from public.patient_invites where token = p_token limit 1; if v_owner is null then raise exception 'Token inválido'; end if; if v_active is not true then raise exception 'Link desativado'; end if; if v_expires is not null and now() > v_expires then raise exception 'Link expirado'; end if; if v_max_uses is not null and v_uses >= v_max_uses then raise exception 'Limite de uso atingido'; end if; if p_name is null or length(trim(p_name)) = 0 then raise exception 'Nome é obrigatório'; end if; insert into public.patient_intake_requests (owner_id, token, name, email, phone, notes, consent, status) values (v_owner, p_token, trim(p_name), nullif(lower(trim(p_email)), ''), nullif(trim(p_phone), ''), nullif(trim(p_notes), ''), coalesce(p_consent, false), 'new') returning id into v_id; update public.patient_invites set uses = uses + 1 where token = p_token; return v_id; end; $$; ALTER FUNCTION public.create_patient_intake_request(p_token text, p_name text, p_email text, p_phone text, p_notes text, p_consent boolean) OWNER TO supabase_admin; -- -- Name: create_patient_intake_request_v2(text, jsonb); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.create_patient_intake_request_v2(p_token text, p_payload jsonb) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $_$ declare v_owner_id uuid; v_intake_id uuid; v_birth_raw text; v_birth date; begin select owner_id into v_owner_id from public.patient_invites where token = p_token; if v_owner_id is null then raise exception 'Token inválido ou expirado'; end if; v_birth_raw := nullif(trim(coalesce( p_payload->>'data_nascimento', '' )), ''); v_birth := case when v_birth_raw is null then null when v_birth_raw ~ '^\d{4}-\d{2}-\d{2}$' then v_birth_raw::date when v_birth_raw ~ '^\d{2}-\d{2}-\d{4}$' then to_date(v_birth_raw, 'DD-MM-YYYY') else null end; insert into public.patient_intake_requests ( owner_id, token, status, consent, nome_completo, email_principal, telefone, avatar_url, -- 🔥 AQUI data_nascimento, cpf, rg, genero, estado_civil, profissao, escolaridade, nacionalidade, naturalidade, cep, pais, cidade, estado, endereco, numero, complemento, bairro, observacoes, notas_internas, encaminhado_por, onde_nos_conheceu ) values ( v_owner_id, p_token, 'new', coalesce((p_payload->>'consent')::boolean, false), nullif(trim(p_payload->>'nome_completo'), ''), nullif(trim(p_payload->>'email_principal'), ''), nullif(regexp_replace(coalesce(p_payload->>'telefone',''), '\D', '', 'g'), ''), nullif(trim(p_payload->>'avatar_url'), ''), -- 🔥 AQUI v_birth, nullif(regexp_replace(coalesce(p_payload->>'cpf',''), '\D', '', 'g'), ''), nullif(trim(p_payload->>'rg'), ''), nullif(trim(p_payload->>'genero'), ''), nullif(trim(p_payload->>'estado_civil'), ''), nullif(trim(p_payload->>'profissao'), ''), nullif(trim(p_payload->>'escolaridade'), ''), nullif(trim(p_payload->>'nacionalidade'), ''), nullif(trim(p_payload->>'naturalidade'), ''), nullif(regexp_replace(coalesce(p_payload->>'cep',''), '\D', '', 'g'), ''), nullif(trim(p_payload->>'pais'), ''), nullif(trim(p_payload->>'cidade'), ''), nullif(trim(p_payload->>'estado'), ''), nullif(trim(p_payload->>'endereco'), ''), nullif(trim(p_payload->>'numero'), ''), nullif(trim(p_payload->>'complemento'), ''), nullif(trim(p_payload->>'bairro'), ''), nullif(trim(p_payload->>'observacoes'), ''), nullif(trim(p_payload->>'notas_internas'), ''), nullif(trim(p_payload->>'encaminhado_por'), ''), nullif(trim(p_payload->>'onde_nos_conheceu'), '') ) returning id into v_intake_id; return v_intake_id; end; $_$; ALTER FUNCTION public.create_patient_intake_request_v2(p_token text, p_payload jsonb) OWNER TO supabase_admin; -- -- Name: create_support_session(uuid, integer); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.create_support_session(p_tenant_id uuid, p_ttl_minutes integer DEFAULT 60) RETURNS json CREATE FUNCTION public.patients_validate_member_consistency() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_tenant_responsible uuid; v_tenant_therapist uuid; BEGIN -- responsible_member sempre deve existir e ser do tenant SELECT tenant_id INTO v_tenant_responsible FROM public.tenant_members WHERE id = NEW.responsible_member_id; IF v_tenant_responsible IS NULL THEN RAISE EXCEPTION 'Responsible member not found'; END IF; IF NEW.tenant_id IS NULL THEN RAISE EXCEPTION 'tenant_id is required'; END IF; IF v_tenant_responsible <> NEW.tenant_id THEN RAISE EXCEPTION 'Responsible member must belong to the same tenant'; END IF; -- therapist scope: therapist_member_id deve existir e ser do mesmo tenant IF NEW.patient_scope = 'therapist' THEN IF NEW.therapist_member_id IS NULL THEN RAISE EXCEPTION 'therapist_member_id is required when patient_scope=therapist'; END IF; SELECT tenant_id INTO v_tenant_therapist FROM public.tenant_members WHERE id = NEW.therapist_member_id; IF v_tenant_therapist IS NULL THEN RAISE EXCEPTION 'Therapist member not found'; END IF; IF v_tenant_therapist <> NEW.tenant_id THEN RAISE EXCEPTION 'Therapist member must belong to the same tenant'; END IF; END IF; RETURN NEW; END; $$; ALTER FUNCTION public.patients_validate_member_consistency() OWNER TO supabase_admin; -- -- Name: patients_validate_responsible_member_tenant(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.patients_validate_responsible_member_tenant() RETURNS trigger LANGUAGE plpgsql AS $$ declare m_tenant uuid; begin select tenant_id into m_tenant from public.tenant_members where id = new.responsible_member_id; if m_tenant is null then raise exception 'Responsible member not found'; end if; if new.tenant_id is null then raise exception 'tenant_id is required'; end if; if m_tenant <> new.tenant_id then raise exception 'Responsible member must belong to the same tenant'; end if; return new; end; $$; ALTER FUNCTION public.patients_validate_responsible_member_tenant() OWNER TO supabase_admin; -- -- Name: populate_notification_queue(); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.populate_notification_queue() RETURNS void CREATE FUNCTION public.prevent_system_group_changes() RETURNS trigger LANGUAGE plpgsql AS $$ begin -- Se for grupo do sistema, regras rígidas: if old.is_system = true then -- nunca pode deletar if tg_op = 'DELETE' then raise exception 'Grupos padrão do sistema não podem ser alterados ou excluídos.'; end if; if tg_op = 'UPDATE' then -- permite SOMENTE mudar tenant_id e/ou updated_at -- qualquer mudança de conteúdo permanece proibida if new.nome is distinct from old.nome or new.descricao is distinct from old.descricao or new.cor is distinct from old.cor or new.is_active is distinct from old.is_active or new.is_system is distinct from old.is_system or new.owner_id is distinct from old.owner_id or new.therapist_id is distinct from old.therapist_id or new.created_at is distinct from old.created_at then raise exception 'Grupos padrão do sistema não podem ser alterados ou excluídos.'; end if; -- chegou aqui: só tenant_id/updated_at mudaram -> ok return new; end if; end if; -- não-system: deixa passar if tg_op = 'DELETE' then return old; end if; return new; end; $$; ALTER FUNCTION public.prevent_system_group_changes() OWNER TO supabase_admin; -- -- Name: provision_account_tenant(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text DEFAULT NULL::text) RETURNS uuid CREATE FUNCTION public.rotate_patient_invite_token(p_new_token text) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ declare v_uid uuid; v_id uuid; begin -- pega o usuário logado v_uid := auth.uid(); if v_uid is null then raise exception 'Usuário não autenticado'; end if; -- desativa tokens antigos ativos do usuário update public.patient_invites set active = false where owner_id = v_uid and active = true; -- cria novo token insert into public.patient_invites (owner_id, token, active) values (v_uid, p_new_token, true) returning id into v_id; return v_id; end; $$; ALTER FUNCTION public.rotate_patient_invite_token(p_new_token text) OWNER TO supabase_admin; -- -- Name: saas_votar_doc(uuid, boolean); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.saas_votar_doc(p_doc_id uuid, p_util boolean) RETURNS jsonb CREATE FUNCTION public.safe_delete_patient(p_patient_id uuid) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN -- Bloqueia se houver histórico IF NOT public.can_delete_patient(p_patient_id) THEN RETURN jsonb_build_object( 'ok', false, 'error', 'has_history', 'message', 'Este paciente possui histórico clínico ou financeiro e não pode ser removido. Você pode desativar ou arquivar o paciente.' ); END IF; -- Verifica ownership via RLS (owner_id ou responsible_member_id) IF NOT EXISTS ( SELECT 1 FROM public.patients WHERE id = p_patient_id AND ( owner_id = auth.uid() OR responsible_member_id IN ( SELECT id FROM public.tenant_members WHERE user_id = auth.uid() ) ) ) THEN RETURN jsonb_build_object( 'ok', false, 'error', 'forbidden', 'message', 'Sem permissão para excluir este paciente.' ); END IF; DELETE FROM public.patients WHERE id = p_patient_id; RETURN jsonb_build_object('ok', true); END; $$; ALTER FUNCTION public.safe_delete_patient(p_patient_id uuid) OWNER TO supabase_admin; -- -- Name: sanitize_phone_br(text); Type: FUNCTION; Schema: public; Owner: supabase_admin -- CREATE FUNCTION public.sanitize_phone_br(raw_phone text) RETURNS text