342defecde
document_generated.documento_id (FK pra documents) estava sempre NULL no INSERT — sem isso nao da pra rastrear qual generated belongs to qual documents row, impossibilitando re-edicao. DocumentGenerate.service saveGeneratedDocument: - Modo create (default): INSERT em documents PRIMEIRO pra capturar doc.id, depois INSERT em document_generated com documento_id setado. - Modo edit (editingDocId param novo): UPDATE in-place — substitui PDF no Storage (novo path), atualiza bucket_path/tamanho/nome em documents (preserva id+audit), atualiza dados_preenchidos+pdf_path em document_generated. Se nao houver registro generated (doc legado), INSERT vinculando ao documents.id. Cleanup best-effort do PDF antigo. - Nova fn loadGeneratedFromDocId(documentoId): busca template_id + dados_preenchidos pra pre-popular o dialog de edicao. useDocumentGenerate.generateAndSave: ganha 2o param editingDocId que passa pro service. Backfill SQL pra docs antigos: match dg.pdf_path = d.bucket_path + tenant/patient guard. 3 docs linkados no DB local, 5 ficaram orfaos (paths que nao existem mais em documents — cleanup antigo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
210 lines
7.0 KiB
JavaScript
210 lines
7.0 KiB
JavaScript
/*
|
|
|--------------------------------------------------------------------------
|
|
| Agência PSI
|
|
|--------------------------------------------------------------------------
|
|
| Criado e desenvolvido por Leonardo Nohama
|
|
|
|
|
| Tecnologia aplicada à escuta.
|
|
| Estrutura para o cuidado.
|
|
|
|
|
| Arquivo: src/features/documents/composables/useDocumentGenerate.js
|
|
| Data: 2026
|
|
| Local: São Carlos/SP — Brasil
|
|
|--------------------------------------------------------------------------
|
|
| © 2026 — Todos os direitos reservados
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
import { ref, computed } from 'vue';
|
|
import {
|
|
loadAllVariables,
|
|
fillTemplate,
|
|
buildFullHtml,
|
|
generatePdfBlob,
|
|
generateAndDownloadPdf,
|
|
printDocument as printPdf,
|
|
saveGeneratedDocument,
|
|
listGeneratedDocuments
|
|
} from '@/services/DocumentGenerate.service';
|
|
import { getTemplate } from '@/services/DocumentTemplates.service';
|
|
|
|
// ── Composable ──────────────────────────────────────────────
|
|
|
|
export function useDocumentGenerate() {
|
|
const loading = ref(false);
|
|
const error = ref(null);
|
|
const generatedDocs = ref([]);
|
|
|
|
// Dados carregados para preenchimento
|
|
const variables = ref({});
|
|
const selectedTemplate = ref(null);
|
|
const previewHtml = ref('');
|
|
|
|
// ── Carregar variaveis do paciente/sessao ───────────────
|
|
|
|
async function loadVariables(patientId, agendaEventoId = null) {
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
variables.value = await loadAllVariables(patientId, agendaEventoId);
|
|
// Hint útil pra diagnostico: se vier objeto mas todos campos vazios,
|
|
// sinaliza que perfil/clínica/paciente provavelmente nao tem dados.
|
|
const filled = Object.values(variables.value).filter(v => String(v ?? '').trim() !== '').length;
|
|
if (filled === 0) {
|
|
error.value = 'Nenhum dado foi encontrado pra auto-preencher. Verifique o cadastro do paciente, perfil e clínica.';
|
|
}
|
|
} catch (e) {
|
|
console.error('[useDocumentGenerate.loadVariables] falha:', e);
|
|
error.value = e?.message || 'Erro ao carregar dados pra preenchimento.';
|
|
variables.value = {};
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
// ── Selecionar template e gerar preview ─────────────────
|
|
|
|
async function selectTemplate(templateId) {
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
selectedTemplate.value = await getTemplate(templateId);
|
|
updatePreview();
|
|
} catch (e) {
|
|
error.value = e?.message || 'Erro ao carregar template.';
|
|
selectedTemplate.value = null;
|
|
previewHtml.value = '';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
// ── Atualizar preview ───────────────────────────────────
|
|
|
|
function updatePreview() {
|
|
if (!selectedTemplate.value) {
|
|
previewHtml.value = '';
|
|
return;
|
|
}
|
|
previewHtml.value = buildFullHtml(selectedTemplate.value, variables.value);
|
|
}
|
|
|
|
// ── Atualizar variavel individual ───────────────────────
|
|
|
|
function setVariable(key, value) {
|
|
variables.value[key] = value;
|
|
updatePreview();
|
|
}
|
|
|
|
// ── Gerar PDF (client-side) ────────────────────────────
|
|
|
|
/**
|
|
* Gera PDF blob, salva no Storage + banco.
|
|
* @param {string} patientId
|
|
* @param {string|null} editingDocId - se setado, UPDATE no doc existente
|
|
* (in-place replace de PDF + metadados, preserva documents.id e audit).
|
|
*/
|
|
async function generateAndSave(patientId, editingDocId = null) {
|
|
if (!selectedTemplate.value) throw new Error('Nenhum template selecionado.');
|
|
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
const templateNome = selectedTemplate.value.nome_template || 'documento';
|
|
|
|
// Gera PDF blob
|
|
const blob = await generatePdfBlob(selectedTemplate.value, variables.value);
|
|
|
|
// Salva no Storage + banco (generated-docs + documents)
|
|
const result = await saveGeneratedDocument({
|
|
templateId: selectedTemplate.value.id,
|
|
patientId,
|
|
dadosPreenchidos: { ...variables.value },
|
|
pdfBlob: blob,
|
|
templateNome,
|
|
templateTipo: selectedTemplate.value.tipo,
|
|
editingDocId
|
|
});
|
|
generatedDocs.value.unshift(result);
|
|
return result;
|
|
} catch (e) {
|
|
error.value = e?.message || 'Erro ao gerar documento.';
|
|
throw e;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gera somente o PDF e faz download, sem salvar no banco.
|
|
*/
|
|
async function downloadOnly() {
|
|
if (!selectedTemplate.value) return;
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
const templateNome = selectedTemplate.value?.nome_template || 'documento';
|
|
const filename = `${templateNome.replace(/\s+/g, '_')}_${Date.now()}.pdf`;
|
|
await generateAndDownloadPdf(selectedTemplate.value, variables.value, filename);
|
|
} catch (e) {
|
|
error.value = e?.message || 'Erro ao gerar PDF.';
|
|
throw e;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Abre PDF em nova aba para impressao.
|
|
*/
|
|
function printDocument() {
|
|
if (!selectedTemplate.value) return;
|
|
printPdf(selectedTemplate.value, variables.value);
|
|
}
|
|
|
|
// ── Carregar historico de documentos gerados ────────────
|
|
|
|
async function fetchGeneratedDocs(patientId) {
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
generatedDocs.value = await listGeneratedDocuments(patientId);
|
|
} catch (e) {
|
|
error.value = e?.message || 'Erro ao carregar documentos gerados.';
|
|
generatedDocs.value = [];
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
// ── Reset ───────────────────────────────────────────────
|
|
|
|
function reset() {
|
|
selectedTemplate.value = null;
|
|
variables.value = {};
|
|
previewHtml.value = '';
|
|
error.value = null;
|
|
}
|
|
|
|
return {
|
|
// State
|
|
loading,
|
|
error,
|
|
variables,
|
|
selectedTemplate,
|
|
previewHtml,
|
|
generatedDocs,
|
|
|
|
// Actions
|
|
loadVariables,
|
|
selectTemplate,
|
|
updatePreview,
|
|
setVariable,
|
|
generateAndSave,
|
|
downloadOnly,
|
|
printDocument,
|
|
fetchGeneratedDocs,
|
|
reset
|
|
};
|
|
}
|