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 { 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 */ }