434 lines
12 KiB
PL/PgSQL
434 lines
12 KiB
PL/PgSQL
-- =============================================================================
|
|
-- 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
|