From 3cbd705249437f258160d6dc9ba10b358aee20d0 Mon Sep 17 00:00:00 2001 From: Manus Date: Sun, 12 Apr 2026 09:49:38 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Correction=20du=20layout=20de=20l?= =?UTF-8?q?a=20zone=20BAP=20dans=20les=20PDFs=20annot=C3=A9s=20:=20mise=20?= =?UTF-8?q?en=20page=202=20colonnes=20(texte=20=C3=A0=20gauche=20/=20signa?= =?UTF-8?q?ture=20=C3=A0=20droite),=20s=C3=A9paration=20du=20destinataire?= =?UTF-8?q?=20sur=20sa=20propre=20ligne,=20suppression=20du=20chevauchemen?= =?UTF-8?q?t=20entre=20le=20nom=20d'=C3=A9tablissement=20et=20l'image=20de?= =?UTF-8?q?=20signature.=20Appliqu=C3=A9=20aux=20deux=20routes=20validateB?= =?UTF-8?q?AP=20et=20validateBAPBulk.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers.ts | 145 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 36 deletions(-) diff --git a/server/routers.ts b/server/routers.ts index 5a0a11a..048c88d 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -455,8 +455,8 @@ export const appRouter = router({ const lastPage = pages[pages.length - 1]; const { width, height } = lastPage.getSize(); - // ── Zone blanche BAP (bas de page, hauteur 140pt) ──────────────── - const zoneHeight = 140; + // ── Zone blanche BAP (bas de page, hauteur 160pt) ──────────────── + const zoneHeight = 160; const zoneX = 30; const zoneY = 10; const zoneW = width - 60; @@ -475,51 +475,80 @@ export const appRouter = router({ const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold); const fontNormal = await pdfDoc.embedFont(StandardFonts.Helvetica); - // Ligne 1 : CAPEX/OPEX | BAP | Destinataire + // Colonne gauche : texte BAP (2/3 de la largeur) + // Colonne droite : signature (1/3 de la largeur) + const leftColW = Math.floor(zoneW * 0.62); + const rightColX = zoneX + leftColW + 10; + const rightColW = zoneW - leftColW - 20; + + // Ligne 1 : CAPEX/OPEX (type achat) const typeAchatText = (invoice.typeAchat || 'N/A').toUpperCase(); - const recipientRaw = (invoice as any).recipientName || ''; - const destinataireText = recipientRaw ? recipientRaw : 'TOUS'; - const line1 = `${typeAchatText} | BON À PAYER | ${destinataireText}`; - const line1Size = 11; - const line1W = font.widthOfTextAtSize(line1, line1Size); - lastPage.drawText(line1, { - x: zoneX + (zoneW - line1W) / 2, + lastPage.drawText(typeAchatText, { + x: zoneX + 10, y: zoneY + zoneHeight - 22, - size: line1Size, + size: 11, font, color: rgb(0.1, 0.1, 0.5), }); - // Séparateur + // Ligne 2 : BON À PAYER (en vert, plus grand) + lastPage.drawText('BON À PAYER', { + x: zoneX + 10, + y: zoneY + zoneHeight - 42, + size: 14, + font, + color: rgb(0, 0.5, 0), + }); + + // Ligne 3 : Destinataire + const recipientRaw = (invoice as any).recipientName || ''; + const destinataireText = recipientRaw ? recipientRaw : 'TOUS'; + lastPage.drawText(destinataireText, { + x: zoneX + 10, + y: zoneY + zoneHeight - 62, + size: 10, + font: fontNormal, + color: rgb(0.2, 0.2, 0.2), + }); + + // Séparateur horizontal lastPage.drawLine({ - start: { x: zoneX + 10, y: zoneY + zoneHeight - 30 }, - end: { x: zoneX + zoneW - 10, y: zoneY + zoneHeight - 30 }, + start: { x: zoneX + 10, y: zoneY + zoneHeight - 72 }, + end: { x: zoneX + leftColW - 10, y: zoneY + zoneHeight - 72 }, thickness: 0.5, color: rgb(0.7, 0.7, 0.7), }); - // Ligne 2 : Service + Ventilation + // Ligne 4 : Service + Ventilation const line2 = `Service : ${invoice.serviceConcerne || '-'} | Ventilation : ${invoice.ventilationComptable || '-'}`; lastPage.drawText(line2, { x: zoneX + 10, - y: zoneY + zoneHeight - 48, + y: zoneY + zoneHeight - 88, size: 9, font: fontNormal, color: rgb(0.2, 0.2, 0.2), }); - // Ligne 3 : Date de validation + // Ligne 5 : Date de validation const now = new Date(); const dateStr = now.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' }); const timeStr = now.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }); lastPage.drawText(`Validé le ${dateStr} à ${timeStr}`, { x: zoneX + 10, - y: zoneY + zoneHeight - 64, + y: zoneY + zoneHeight - 104, size: 8, font: fontNormal, color: rgb(0.4, 0.4, 0.4), }); + // Séparateur vertical entre colonne gauche et droite + lastPage.drawLine({ + start: { x: zoneX + leftColW, y: zoneY + 10 }, + end: { x: zoneX + leftColW, y: zoneY + zoneHeight - 10 }, + thickness: 0.5, + color: rgb(0.8, 0.8, 0.8), + }); + // ── Signature du service ────────────────────────────────────────── const serviceAssociations = await getServiceSignaturesByUser(ctx.user.id); const serviceName = invoice.serviceConcerne || ''; @@ -555,17 +584,22 @@ export const appRouter = router({ } else { embeddedSig = await pdfDoc.embedJpg(sigImageBytes); } - const sigWidth = 100; - const sigHeight = 45; + // Signature dans la colonne droite, centrée verticalement + const sigWidth = Math.min(rightColW - 10, 110); + const sigHeight = Math.round(sigWidth * 0.45); + const sigX = rightColX + (rightColW - sigWidth) / 2; + const sigY = zoneY + 35; lastPage.drawImage(embeddedSig, { - x: zoneX + zoneW - sigWidth - 10, - y: zoneY + 20, + x: sigX, + y: sigY, width: sigWidth, height: sigHeight, }); + // Nom du signataire centré sous la signature + const nameW = fontNormal.widthOfTextAtSize(signatureName, 8); lastPage.drawText(signatureName, { - x: zoneX + zoneW - sigWidth - 10, - y: zoneY + 12, + x: sigX + (sigWidth - nameW) / 2, + y: zoneY + 22, size: 8, font: fontNormal, color: rgb(0.3, 0.3, 0.3), @@ -686,30 +720,63 @@ export const appRouter = router({ const { width, height } = lastPage.getSize(); const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold); const fontNormal = await pdfDoc.embedFont(StandardFonts.Helvetica); - const zoneH = 120; - const zoneW = 200; - const zoneX = width - zoneW - 20; - const zoneY = 20; + // Zone BAP en bas à droite, layout 2 colonnes + const zoneH = 160; + const zoneW = width - 60; + const zoneX = 30; + const zoneY = 10; + const leftColW = Math.floor(zoneW * 0.62); + const rightColX = zoneX + leftColW + 10; + const rightColW = zoneW - leftColW - 20; lastPage.drawRectangle({ x: zoneX, y: zoneY, width: zoneW, height: zoneH, color: rgb(1, 1, 1), borderColor: rgb(0.2, 0.2, 0.2), borderWidth: 1, }); - const typeAchatText = invoice.typeAchat || ''; + // Ligne 1 : Type achat + const typeAchatText = (invoice.typeAchat || '').toUpperCase(); lastPage.drawText(typeAchatText, { - x: zoneX + 10, y: zoneY + zoneH - 20, - size: 12, font: fontBold, color: rgb(0, 0, 0), + x: zoneX + 10, y: zoneY + zoneH - 22, + size: 11, font: fontBold, color: rgb(0.1, 0.1, 0.5), }); + // Ligne 2 : BON À PAYER lastPage.drawText('BON À PAYER', { - x: zoneX + 10, y: zoneY + zoneH - 40, - size: 14, font: fontBold, color: rgb(0, 0.4, 0), + x: zoneX + 10, y: zoneY + zoneH - 42, + size: 14, font: fontBold, color: rgb(0, 0.5, 0), }); + // Ligne 3 : Destinataire const recipientText = (invoice as any).recipientName || 'TOUS'; lastPage.drawText(recipientText, { - x: zoneX + 10, y: zoneY + zoneH - 60, + x: zoneX + 10, y: zoneY + zoneH - 62, size: 10, font: fontNormal, color: rgb(0.2, 0.2, 0.2), }); + // Séparateur horizontal + lastPage.drawLine({ + start: { x: zoneX + 10, y: zoneY + zoneH - 72 }, + end: { x: zoneX + leftColW - 10, y: zoneY + zoneH - 72 }, + thickness: 0.5, color: rgb(0.7, 0.7, 0.7), + }); + // Ligne 4 : Service + Ventilation + const serviceVentLine = `Service : ${invoice.serviceConcerne || '-'} | Ventilation : ${invoice.ventilationComptable || '-'}`; + lastPage.drawText(serviceVentLine, { + x: zoneX + 10, y: zoneY + zoneH - 88, + size: 9, font: fontNormal, color: rgb(0.2, 0.2, 0.2), + }); + // Ligne 5 : Date de validation + const bulkNow = new Date(); + const bulkDateStr = bulkNow.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' }); + const bulkTimeStr = bulkNow.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }); + lastPage.drawText(`Validé le ${bulkDateStr} à ${bulkTimeStr}`, { + x: zoneX + 10, y: zoneY + zoneH - 104, + size: 8, font: fontNormal, color: rgb(0.4, 0.4, 0.4), + }); + // Séparateur vertical + lastPage.drawLine({ + start: { x: zoneX + leftColW, y: zoneY + 10 }, + end: { x: zoneX + leftColW, y: zoneY + zoneH - 10 }, + thickness: 0.5, color: rgb(0.8, 0.8, 0.8), + }); // Signature du service const serviceName = invoice.serviceConcerne || ''; const assoc = serviceSignaturesList.find( @@ -740,11 +807,17 @@ export const appRouter = router({ const embeddedSig = mimeType === 'image/png' ? await pdfDoc.embedPng(sigImageBytes) : await pdfDoc.embedJpg(sigImageBytes); + // Signature dans la colonne droite, centrée + const bulkSigWidth = Math.min(rightColW - 10, 110); + const bulkSigHeight = Math.round(bulkSigWidth * 0.45); + const bulkSigX = rightColX + (rightColW - bulkSigWidth) / 2; + const bulkSigY = zoneY + 35; lastPage.drawImage(embeddedSig, { - x: zoneX + zoneW - 110, y: zoneY + 20, width: 100, height: 45, + x: bulkSigX, y: bulkSigY, width: bulkSigWidth, height: bulkSigHeight, }); + const bulkNameW = fontNormal.widthOfTextAtSize(signatureName, 8); lastPage.drawText(signatureName, { - x: zoneX + zoneW - 110, y: zoneY + 12, + x: bulkSigX + (bulkSigWidth - bulkNameW) / 2, y: zoneY + 22, size: 8, font: fontNormal, color: rgb(0.3, 0.3, 0.3), }); } catch (_) { /* ignore signature errors */ }