Checkpoint: Correction du layout de la zone BAP dans les PDFs annotés : mise en page 2 colonnes (texte à gauche / signature à droite), séparation du destinataire sur sa propre ligne, suppression du chevauchement entre le nom d'établissement et l'image de signature. Appliqué aux deux routes validateBAP et validateBAPBulk.

This commit is contained in:
Manus
2026-04-12 09:49:38 -04:00
parent 76554214b2
commit 3cbd705249

View File

@@ -455,8 +455,8 @@ export const appRouter = router({
const lastPage = pages[pages.length - 1]; const lastPage = pages[pages.length - 1];
const { width, height } = lastPage.getSize(); const { width, height } = lastPage.getSize();
// ── Zone blanche BAP (bas de page, hauteur 140pt) ──────────────── // ── Zone blanche BAP (bas de page, hauteur 160pt) ────────────────
const zoneHeight = 140; const zoneHeight = 160;
const zoneX = 30; const zoneX = 30;
const zoneY = 10; const zoneY = 10;
const zoneW = width - 60; const zoneW = width - 60;
@@ -475,51 +475,80 @@ export const appRouter = router({
const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold); const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const fontNormal = await pdfDoc.embedFont(StandardFonts.Helvetica); 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 typeAchatText = (invoice.typeAchat || 'N/A').toUpperCase();
const recipientRaw = (invoice as any).recipientName || ''; lastPage.drawText(typeAchatText, {
const destinataireText = recipientRaw ? recipientRaw : 'TOUS'; x: zoneX + 10,
const line1 = `${typeAchatText} | BON À PAYER | ${destinataireText}`;
const line1Size = 11;
const line1W = font.widthOfTextAtSize(line1, line1Size);
lastPage.drawText(line1, {
x: zoneX + (zoneW - line1W) / 2,
y: zoneY + zoneHeight - 22, y: zoneY + zoneHeight - 22,
size: line1Size, size: 11,
font, font,
color: rgb(0.1, 0.1, 0.5), 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({ lastPage.drawLine({
start: { x: zoneX + 10, y: zoneY + zoneHeight - 30 }, start: { x: zoneX + 10, y: zoneY + zoneHeight - 72 },
end: { x: zoneX + zoneW - 10, y: zoneY + zoneHeight - 30 }, end: { x: zoneX + leftColW - 10, y: zoneY + zoneHeight - 72 },
thickness: 0.5, thickness: 0.5,
color: rgb(0.7, 0.7, 0.7), color: rgb(0.7, 0.7, 0.7),
}); });
// Ligne 2 : Service + Ventilation // Ligne 4 : Service + Ventilation
const line2 = `Service : ${invoice.serviceConcerne || '-'} | Ventilation : ${invoice.ventilationComptable || '-'}`; const line2 = `Service : ${invoice.serviceConcerne || '-'} | Ventilation : ${invoice.ventilationComptable || '-'}`;
lastPage.drawText(line2, { lastPage.drawText(line2, {
x: zoneX + 10, x: zoneX + 10,
y: zoneY + zoneHeight - 48, y: zoneY + zoneHeight - 88,
size: 9, size: 9,
font: fontNormal, font: fontNormal,
color: rgb(0.2, 0.2, 0.2), color: rgb(0.2, 0.2, 0.2),
}); });
// Ligne 3 : Date de validation // Ligne 5 : Date de validation
const now = new Date(); const now = new Date();
const dateStr = now.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' }); 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' }); const timeStr = now.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
lastPage.drawText(`Validé le ${dateStr} à ${timeStr}`, { lastPage.drawText(`Validé le ${dateStr} à ${timeStr}`, {
x: zoneX + 10, x: zoneX + 10,
y: zoneY + zoneHeight - 64, y: zoneY + zoneHeight - 104,
size: 8, size: 8,
font: fontNormal, font: fontNormal,
color: rgb(0.4, 0.4, 0.4), 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 ────────────────────────────────────────── // ── Signature du service ──────────────────────────────────────────
const serviceAssociations = await getServiceSignaturesByUser(ctx.user.id); const serviceAssociations = await getServiceSignaturesByUser(ctx.user.id);
const serviceName = invoice.serviceConcerne || ''; const serviceName = invoice.serviceConcerne || '';
@@ -555,17 +584,22 @@ export const appRouter = router({
} else { } else {
embeddedSig = await pdfDoc.embedJpg(sigImageBytes); embeddedSig = await pdfDoc.embedJpg(sigImageBytes);
} }
const sigWidth = 100; // Signature dans la colonne droite, centrée verticalement
const sigHeight = 45; 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, { lastPage.drawImage(embeddedSig, {
x: zoneX + zoneW - sigWidth - 10, x: sigX,
y: zoneY + 20, y: sigY,
width: sigWidth, width: sigWidth,
height: sigHeight, height: sigHeight,
}); });
// Nom du signataire centré sous la signature
const nameW = fontNormal.widthOfTextAtSize(signatureName, 8);
lastPage.drawText(signatureName, { lastPage.drawText(signatureName, {
x: zoneX + zoneW - sigWidth - 10, x: sigX + (sigWidth - nameW) / 2,
y: zoneY + 12, y: zoneY + 22,
size: 8, size: 8,
font: fontNormal, font: fontNormal,
color: rgb(0.3, 0.3, 0.3), color: rgb(0.3, 0.3, 0.3),
@@ -686,30 +720,63 @@ export const appRouter = router({
const { width, height } = lastPage.getSize(); const { width, height } = lastPage.getSize();
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold); const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const fontNormal = await pdfDoc.embedFont(StandardFonts.Helvetica); const fontNormal = await pdfDoc.embedFont(StandardFonts.Helvetica);
const zoneH = 120; // Zone BAP en bas à droite, layout 2 colonnes
const zoneW = 200; const zoneH = 160;
const zoneX = width - zoneW - 20; const zoneW = width - 60;
const zoneY = 20; 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({ lastPage.drawRectangle({
x: zoneX, y: zoneY, width: zoneW, height: zoneH, x: zoneX, y: zoneY, width: zoneW, height: zoneH,
color: rgb(1, 1, 1), color: rgb(1, 1, 1),
borderColor: rgb(0.2, 0.2, 0.2), borderColor: rgb(0.2, 0.2, 0.2),
borderWidth: 1, borderWidth: 1,
}); });
const typeAchatText = invoice.typeAchat || ''; // Ligne 1 : Type achat
const typeAchatText = (invoice.typeAchat || '').toUpperCase();
lastPage.drawText(typeAchatText, { lastPage.drawText(typeAchatText, {
x: zoneX + 10, y: zoneY + zoneH - 20, x: zoneX + 10, y: zoneY + zoneH - 22,
size: 12, font: fontBold, color: rgb(0, 0, 0), size: 11, font: fontBold, color: rgb(0.1, 0.1, 0.5),
}); });
// Ligne 2 : BON À PAYER
lastPage.drawText('BON À PAYER', { lastPage.drawText('BON À PAYER', {
x: zoneX + 10, y: zoneY + zoneH - 40, x: zoneX + 10, y: zoneY + zoneH - 42,
size: 14, font: fontBold, color: rgb(0, 0.4, 0), size: 14, font: fontBold, color: rgb(0, 0.5, 0),
}); });
// Ligne 3 : Destinataire
const recipientText = (invoice as any).recipientName || 'TOUS'; const recipientText = (invoice as any).recipientName || 'TOUS';
lastPage.drawText(recipientText, { 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), 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 // Signature du service
const serviceName = invoice.serviceConcerne || ''; const serviceName = invoice.serviceConcerne || '';
const assoc = serviceSignaturesList.find( const assoc = serviceSignaturesList.find(
@@ -740,11 +807,17 @@ export const appRouter = router({
const embeddedSig = mimeType === 'image/png' const embeddedSig = mimeType === 'image/png'
? await pdfDoc.embedPng(sigImageBytes) ? await pdfDoc.embedPng(sigImageBytes)
: await pdfDoc.embedJpg(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, { 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, { 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), size: 8, font: fontNormal, color: rgb(0.3, 0.3, 0.3),
}); });
} catch (_) { /* ignore signature errors */ } } catch (_) { /* ignore signature errors */ }