From d07ff1b34db836a47de2f0094cec350c6e4b53f0 Mon Sep 17 00:00:00 2001 From: Manus Date: Sun, 12 Apr 2026 06:17:38 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Correction=20critique=20:=20la=20?= =?UTF-8?q?route=20validateBAP=20et=20exportToPdf=20lisent=20maintenant=20?= =?UTF-8?q?le=20PDF=20source=20et=20l'image=20de=20signature=20depuis=20l'?= =?UTF-8?q?URL=20publique=20en=20fallback=20si=20le=20fichier=20local=20n'?= =?UTF-8?q?est=20pas=20disponible=20(cas=20du=20VPS=20o=C3=B9=20les=20fich?= =?UTF-8?q?iers=20sont=20stock=C3=A9s=20localement=20mais=20pas=20dans=20l?= =?UTF-8?q?a=20sandbox).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers.ts | 91 +++++++++++++++++++++++++++++++++++++++++------ todo.md | 6 ++++ 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/server/routers.ts b/server/routers.ts index e84aff0..fb52042 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -428,9 +428,28 @@ export const appRouter = router({ let signatureName: string | null = null; try { - if (!invoice.fileKey) throw new Error('Fichier PDF source introuvable'); - const sourcePath = path.join(STORAGE_BASE_PATH, invoice.fileKey); - const pdfBytes = await fs.readFile(sourcePath); + if (!invoice.fileKey && !invoice.fileUrl) throw new Error('Fichier PDF source introuvable'); + let pdfBytes: Buffer; + // Essayer d'abord le chemin local, puis l'URL publique + const sourcePath = path.join(STORAGE_BASE_PATH, invoice.fileKey || ''); + try { + pdfBytes = await fs.readFile(sourcePath); + } catch (_localErr) { + // Fichier non disponible localement → télécharger depuis l'URL + const fileUrl = invoice.fileUrl; + if (!fileUrl) throw new Error('Fichier PDF source introuvable (local et URL)'); + // Construire l'URL absolue si relative + let absoluteUrl = fileUrl; + if (fileUrl.startsWith('/')) { + const baseUrl = process.env.APP_BASE_URL || `http://localhost:${process.env.PORT || 3000}`; + absoluteUrl = `${baseUrl}${fileUrl}`; + } + // Node.js 22 a fetch natif + const response = await fetch(absoluteUrl); + if (!response.ok) throw new Error(`Impossible de télécharger le PDF: ${response.status}`); + const arrayBuffer = await response.arrayBuffer(); + pdfBytes = Buffer.from(arrayBuffer); + } const pdfDoc = await PDFDocument.load(pdfBytes); const pages = pdfDoc.getPages(); const lastPage = pages[pages.length - 1]; @@ -512,8 +531,23 @@ export const appRouter = router({ if (sig) { signatureName = `${sig.firstName} ${sig.lastName}`; try { + let sigImageBytes: Buffer; const sigImagePath = path.join(STORAGE_BASE_PATH, sig.imageKey); - const sigImageBytes = await fs.readFile(sigImagePath); + try { + sigImageBytes = await fs.readFile(sigImagePath); + } catch (_sigLocalErr) { + // Fichier signature non disponible localement → télécharger depuis l'URL + const sigUrl = sig.imageUrl; + if (!sigUrl) throw new Error('Image signature introuvable'); + let absoluteSigUrl = sigUrl; + if (sigUrl.startsWith('/')) { + const baseUrl = process.env.APP_BASE_URL || `http://localhost:${process.env.PORT || 3000}`; + absoluteSigUrl = `${baseUrl}${sigUrl}`; + } + const sigResp = await fetch(absoluteSigUrl); + if (!sigResp.ok) throw new Error(`Impossible de télécharger la signature: ${sigResp.status}`); + sigImageBytes = Buffer.from(await sigResp.arrayBuffer()); + } const mimeType = sig.imageKey.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg'; let embeddedSig; if (mimeType === 'image/png') { @@ -835,15 +869,35 @@ export const appRouter = router({ const sig = await getSignatureById(assoc.signatureId); if (sig) { const sigImagePath = path.join(STORAGE_BASE_PATH, sig.imageKey); - const pdfBytes = await fs.readFile(sourcePath); + // Lire le PDF source (local ou URL) + let pdfBytes: Buffer; + try { + pdfBytes = await fs.readFile(sourcePath); + } catch (_) { + const fileUrl = invoice!.fileUrl; + if (!fileUrl) throw new Error('PDF source introuvable'); + let absUrl = fileUrl.startsWith('/') ? `${process.env.APP_BASE_URL || `http://localhost:${process.env.PORT || 3000}`}${fileUrl}` : fileUrl; + const r = await fetch(absUrl); + if (!r.ok) throw new Error(`HTTP ${r.status}`); + pdfBytes = Buffer.from(await r.arrayBuffer()); + } const pdfDoc = await PDFDocument.load(pdfBytes); const pages = pdfDoc.getPages(); const lastPage = pages[pages.length - 1]; const { width, height } = lastPage.getSize(); - // Read signature image - const sigImageBytes = await fs.readFile(sigImagePath); - const sigImageBase64 = sigImageBytes.toString('base64'); + // Read signature image (local ou URL) + let sigImageBytes: Buffer; + try { + sigImageBytes = await fs.readFile(sigImagePath); + } catch (_) { + const sigUrl = sig.imageUrl; + if (!sigUrl) throw new Error('Signature introuvable'); + let absSigUrl = sigUrl.startsWith('/') ? `${process.env.APP_BASE_URL || `http://localhost:${process.env.PORT || 3000}`}${sigUrl}` : sigUrl; + const sr = await fetch(absSigUrl); + if (!sr.ok) throw new Error(`HTTP ${sr.status}`); + sigImageBytes = Buffer.from(await sr.arrayBuffer()); + } const mimeType = sig.imageKey.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg'; let embeddedSig; @@ -880,17 +934,32 @@ export const appRouter = router({ console.log(`[Export] Signature apposée pour ${serviceName} sur ${filename}`); } else { // Signature not found, copy without signature - await fs.copyFile(sourcePath, destPath); + try { await fs.copyFile(sourcePath, destPath); } catch (_) { + const fu = invoice!.fileUrl; if (!fu) throw new Error('PDF introuvable'); + const au = fu.startsWith('/') ? `${process.env.APP_BASE_URL||`http://localhost:${process.env.PORT||3000}`}${fu}` : fu; + const rb = await fetch(au); if (!rb.ok) throw new Error(`HTTP ${rb.status}`); + await fs.writeFile(destPath, Buffer.from(await rb.arrayBuffer())); + } copiedFiles.push(destPath); } } catch (sigError: any) { console.warn(`[Export] Impossible d'apposer la signature: ${sigError.message}. Copie sans signature.`); - await fs.copyFile(sourcePath, destPath); + try { await fs.copyFile(sourcePath, destPath); } catch (_) { + const fu = invoice!.fileUrl; if (!fu) throw new Error('PDF introuvable'); + const au = fu.startsWith('/') ? `${process.env.APP_BASE_URL||`http://localhost:${process.env.PORT||3000}`}${fu}` : fu; + const rb = await fetch(au); if (!rb.ok) throw new Error(`HTTP ${rb.status}`); + await fs.writeFile(destPath, Buffer.from(await rb.arrayBuffer())); + } copiedFiles.push(destPath); } } else { // No signature association, copy as-is - await fs.copyFile(sourcePath, destPath); + try { await fs.copyFile(sourcePath, destPath); } catch (_) { + const fu = invoice!.fileUrl; if (!fu) throw new Error('PDF introuvable'); + const au = fu.startsWith('/') ? `${process.env.APP_BASE_URL||`http://localhost:${process.env.PORT||3000}`}${fu}` : fu; + const rb = await fetch(au); if (!rb.ok) throw new Error(`HTTP ${rb.status}`); + await fs.writeFile(destPath, Buffer.from(await rb.arrayBuffer())); + } copiedFiles.push(destPath); } diff --git a/todo.md b/todo.md index 33ea972..b5e3d11 100644 --- a/todo.md +++ b/todo.md @@ -597,3 +597,9 @@ - [x] Ajouter table bapHistory en base de données (schéma + migration) - [x] Enregistrer chaque validation BAP dans l'historique - [x] Ajouter la route dans App.tsx et le lien dans le menu Traçabilité + +## Correction génération PDF BAP (fallback URL) +- [x] Corriger la lecture du PDF source dans validateBAP (fallback URL si fichier local absent) +- [x] Corriger la lecture de l'image de signature dans validateBAP (fallback URL) +- [x] Corriger la lecture du PDF source dans exportToPdf (fallback URL) +- [x] Corriger la lecture de l'image de signature dans exportToPdf (fallback URL)