compliance CFP #7: portal + fluxo de assinatura no SharedDocumentPage
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>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
-- ============================================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user