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:
@@ -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 */ }
|
||||
|
||||
Reference in New Issue
Block a user