934c620295
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>
150 lines
4.7 KiB
JavaScript
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
|
|
};
|
|
}
|