Checkpoint: Correction critique : la route validateBAP et exportToPdf lisent maintenant le PDF source et l'image de signature depuis l'URL publique en fallback si le fichier local n'est pas disponible (cas du VPS où les fichiers sont stockés localement mais pas dans la sandbox).

This commit is contained in:
Manus
2026-04-12 06:17:38 -04:00
parent 5f7c8c7c7f
commit d07ff1b34d
2 changed files with 86 additions and 11 deletions

View File

@@ -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);
}

View File

@@ -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)