4e42881d5e
ROADMAP #1.2 #7 — Assinatura eletronica no portal. Migration 20260521000007 cria RPC list_my_signatures (SECURITY DEFINER) que cruza auth.uid() por 3 caminhos (signatario_id, signatario_email, patient.user_id) e devolve solicitacoes pendentes + share_token pra link de assinatura. service.listMySignatures wrappa a RPC. Composable useDocumentSignatures ganha loadMine(). PortalDocumentos.vue (nova) — lista signatures do paciente logado com KPIs (total/pendentes/assinados/recusados), filtro, e botao "Assinar agora" que navega pra /shared/document/:token. Item no portal.menu "Documentos > Para assinar". SharedDocumentPage.vue estendida: agora chama getSignableDocumentBy Token primeiro (RPC nova). Quando o documento tem signatures pendentes, mostra painel azul abaixo do preview com: - Aviso LGPD/CFP explicando o que sera registrado (IP/UA/timestamp/hash) - Checkbox aceite obrigatorio - Selecao de signatario quando multi-signatario - Botoes Assinar/Recusar com loading state - Computacao SHA-256 server-fetched antes do click Fluxo: terapeuta gera doc -> cria signature + share_link -> link e listado em /portal/documentos -> paciente clica -> /shared/document/ :token mostra doc + painel -> aceite -> assinatura registrada via RPC sign_document_by_token (IP/UA capturados server-side). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
3.6 KiB
PL/PgSQL
103 lines
3.6 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Compliance CFP #7 — RPC list_my_signatures (portal do paciente)
|
|
-- ----------------------------------------------------------------------------
|
|
-- Retorna as solicitações de assinatura do paciente logado (auth.uid()
|
|
-- associado a patients.user_id). SECURITY DEFINER pra bypassar a RLS de
|
|
-- document_signatures (que hoje só libera pra tenant_members).
|
|
--
|
|
-- Cada item já vem com o share_link.token associado, pra que o portal
|
|
-- aponte direto pra /shared/document/:token onde o usuário vai assinar.
|
|
-- O link público é gerado quando o terapeuta solicita a assinatura.
|
|
-- ============================================================================
|
|
|
|
BEGIN;
|
|
|
|
CREATE OR REPLACE FUNCTION public.list_my_signatures(
|
|
p_status text[] DEFAULT NULL
|
|
) RETURNS TABLE (
|
|
signature_id uuid,
|
|
documento_id uuid,
|
|
tenant_id uuid,
|
|
signatario_tipo text,
|
|
status text,
|
|
ordem smallint,
|
|
assinado_em timestamptz,
|
|
criado_em timestamptz,
|
|
-- Documento
|
|
nome_original text,
|
|
tipo_documento text,
|
|
mime_type text,
|
|
-- Share link (primeiro válido encontrado pro doc)
|
|
share_token text,
|
|
share_expira_em timestamptz
|
|
)
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
v_uid uuid;
|
|
BEGIN
|
|
v_uid := auth.uid();
|
|
IF v_uid IS NULL THEN
|
|
RAISE EXCEPTION 'Sessão inválida' USING ERRCODE = '28000';
|
|
END IF;
|
|
|
|
RETURN QUERY
|
|
SELECT
|
|
s.id AS signature_id,
|
|
s.documento_id AS documento_id,
|
|
s.tenant_id AS tenant_id,
|
|
s.signatario_tipo AS signatario_tipo,
|
|
s.status AS status,
|
|
s.ordem AS ordem,
|
|
s.assinado_em AS assinado_em,
|
|
s.criado_em AS criado_em,
|
|
d.nome_original AS nome_original,
|
|
d.tipo_documento AS tipo_documento,
|
|
d.mime_type AS mime_type,
|
|
sl.token AS share_token,
|
|
sl.expira_em AS share_expira_em
|
|
FROM public.document_signatures s
|
|
JOIN public.documents d ON d.id = s.documento_id AND d.deleted_at IS NULL
|
|
LEFT JOIN LATERAL (
|
|
SELECT token, expira_em
|
|
FROM public.document_share_links
|
|
WHERE documento_id = d.id
|
|
AND ativo = true
|
|
AND expira_em > now()
|
|
AND usos < usos_max
|
|
ORDER BY criado_em DESC
|
|
LIMIT 1
|
|
) sl ON true
|
|
WHERE (
|
|
-- signatario_id direto (quando registrado)
|
|
s.signatario_id = v_uid
|
|
OR
|
|
-- Fallback: paciente pelo email (quando signatario_id veio NULL)
|
|
s.signatario_email = (SELECT email FROM auth.users WHERE id = v_uid)
|
|
OR
|
|
-- Fallback: paciente pelo patient_id (documents.patient_id -> patients.user_id)
|
|
d.patient_id IN (SELECT p.id FROM public.patients p WHERE p.user_id = v_uid)
|
|
)
|
|
AND (p_status IS NULL OR s.status = ANY (p_status))
|
|
ORDER BY
|
|
CASE s.status
|
|
WHEN 'pendente' THEN 0
|
|
WHEN 'enviado' THEN 1
|
|
WHEN 'assinado' THEN 2
|
|
WHEN 'recusado' THEN 3
|
|
WHEN 'expirado' THEN 4
|
|
ELSE 99
|
|
END,
|
|
s.criado_em DESC;
|
|
END;
|
|
$$;
|
|
|
|
COMMENT ON FUNCTION public.list_my_signatures(text[]) IS
|
|
'Lista signatures do paciente logado (auth.uid()) cruzando por signatario_id, email ou patient.user_id. Inclui share_token pra link de assinatura.';
|
|
|
|
GRANT EXECUTE ON FUNCTION public.list_my_signatures(text[]) TO authenticated;
|
|
|
|
COMMIT;
|