Files
agenciapsilmno/src/features/documents/composables/useDocumentSignatures.js
T
Leonardo 934c620295 compliance CFP #7: RPCs de assinatura + service ext + composable
Backend foundation pra assinatura eletronica (ROADMAP #1.2 #7).

Migration 20260521000006 cria 3 RPCs:
  - sign_document_by_signature_id (paciente logado, SECURITY INVOKER)
  - sign_document_by_token        (terceiro via share link, SECURITY DEFINER)
  - get_signable_document_by_token (preview pre-assinatura)

IP + user-agent capturados SERVER-SIDE via inet_client_addr() e
current_setting('request.headers'). Hash SHA-256 vem do cliente
pra integridade. Token via share link incrementa usos no UPDATE.

DocumentSignatures.service estendido com 3 wrappers RPC: signByPortal,
signByToken, getSignableDocumentByToken. useDocumentSignatures composable
novo (Tipo A blueprint) expoe state reativo + acoes: fetchForDocument,
requestSignatures, sign, refuse, signWithToken, loadByToken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 04:39:05 -03:00

150 lines
4.7 KiB
JavaScript

/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/features/documents/composables/useDocumentSignatures.js
| Composable Tipo A (thin wrapper) sobre DocumentSignatures.service.
|--------------------------------------------------------------------------
*/
import { ref } from 'vue';
import {
createSignatureRequests,
listSignatures,
getSignatureStatus,
refuseSignature,
signByPortal,
signByToken,
getSignableDocumentByToken,
hashDocument
} from '@/services/DocumentSignatures.service';
export function useDocumentSignatures() {
const signatures = ref([]);
const loading = ref(false);
const error = ref('');
const status = ref(null); // { total, assinados, pendentes, status }
async function fetchForDocument(documentoId) {
if (!documentoId) {
signatures.value = [];
status.value = null;
return [];
}
loading.value = true;
error.value = '';
try {
const [list, st] = await Promise.all([
listSignatures(documentoId),
getSignatureStatus(documentoId)
]);
signatures.value = Array.isArray(list) ? list : [];
status.value = st || null;
return signatures.value;
} catch (e) {
error.value = e?.message || 'Falha ao carregar assinaturas.';
signatures.value = [];
status.value = null;
throw e;
} finally {
loading.value = false;
}
}
async function requestSignatures(documentoId, signatarios = []) {
loading.value = true;
error.value = '';
try {
const rows = await createSignatureRequests(documentoId, signatarios);
signatures.value = [...signatures.value, ...rows];
return rows;
} catch (e) {
error.value = e?.message || 'Falha ao solicitar assinaturas.';
throw e;
} finally {
loading.value = false;
}
}
async function sign(signatureId, { hashDocumento = null } = {}) {
loading.value = true;
error.value = '';
try {
const updated = await signByPortal(signatureId, hashDocumento);
const idx = signatures.value.findIndex(s => s.id === signatureId);
if (idx >= 0) signatures.value.splice(idx, 1, updated);
return updated;
} catch (e) {
error.value = e?.message || 'Falha ao assinar documento.';
throw e;
} finally {
loading.value = false;
}
}
async function refuse(signatureId) {
loading.value = true;
error.value = '';
try {
const updated = await refuseSignature(signatureId);
const idx = signatures.value.findIndex(s => s.id === signatureId);
if (idx >= 0) signatures.value.splice(idx, 1, updated);
return updated;
} catch (e) {
error.value = e?.message || 'Falha ao recusar assinatura.';
throw e;
} finally {
loading.value = false;
}
}
async function signWithToken(token, signatureId = null, { hashDocumento = null } = {}) {
loading.value = true;
error.value = '';
try {
return await signByToken(token, signatureId, hashDocumento);
} catch (e) {
error.value = e?.message || 'Falha ao assinar via link.';
throw e;
} finally {
loading.value = false;
}
}
async function loadByToken(token) {
loading.value = true;
error.value = '';
try {
const payload = await getSignableDocumentByToken(token);
if (!payload?.valid) {
error.value = payload?.error === 'expired_or_invalid'
? 'Link expirado ou inválido.'
: payload?.error === 'document_not_found'
? 'Documento não encontrado.'
: 'Token inválido.';
return null;
}
signatures.value = Array.isArray(payload.signatures) ? payload.signatures : [];
return payload;
} catch (e) {
error.value = e?.message || 'Falha ao validar token.';
throw e;
} finally {
loading.value = false;
}
}
return {
signatures,
status,
loading,
error,
fetchForDocument,
requestSignatures,
sign,
refuse,
signWithToken,
loadByToken,
hashDocument
};
}