fix: PDF FreePro dessin manuel - 1 page A4 portrait garantie
This commit is contained in:
@@ -100,92 +100,105 @@ function buildPdfDoc(importRecord: ImportRecord, lines: VentilationLine[]): jsPD
|
|||||||
const pageW = 210;
|
const pageW = 210;
|
||||||
const pageH = 297;
|
const pageH = 297;
|
||||||
const margin = 14;
|
const margin = 14;
|
||||||
const headerEndY = 33; // Y après l'en-tête
|
const tableStartY = 31; // Y où commence le tableau
|
||||||
const footerH = 8; // marge basse
|
const tableEndY = pageH - 10; // Y max du tableau
|
||||||
|
const usableW = pageW - margin * 2; // 182mm
|
||||||
|
|
||||||
// En-tête compact
|
// ── En-tête ────────────────────────────────────────────────────────────────
|
||||||
doc.setFontSize(13);
|
|
||||||
doc.setFont("helvetica", "bold");
|
|
||||||
doc.text("Ventilation facture FREE PRO", pageW / 2, 12, { align: "center" });
|
|
||||||
|
|
||||||
// Date du mois
|
|
||||||
const [moisStr, anneeStr] = importRecord.moisLabel.split("/");
|
|
||||||
const dateLabel = `01/${moisStr}/${anneeStr}`;
|
|
||||||
doc.setFontSize(10);
|
|
||||||
doc.text(dateLabel, pageW / 2, 19, { align: "center" });
|
|
||||||
|
|
||||||
// Date d'édition (haut droite)
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const editDate = `Edite le ${now.toLocaleDateString("fr-FR")} - ${now.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })}`;
|
const editDate = `Edite le ${now.toLocaleDateString("fr-FR")} - ${now.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })}`;
|
||||||
doc.setFontSize(7);
|
doc.setFontSize(7);
|
||||||
doc.setFont("helvetica", "normal");
|
doc.setFont("helvetica", "normal");
|
||||||
doc.text(editDate, pageW - margin, 8, { align: "right" });
|
doc.text(editDate, pageW - margin, 7, { align: "right" });
|
||||||
|
|
||||||
|
doc.setFontSize(13);
|
||||||
|
doc.setFont("helvetica", "bold");
|
||||||
|
doc.text("Ventilation facture FREE PRO", pageW / 2, 13, { align: "center" });
|
||||||
|
|
||||||
|
const [moisStr, anneeStr] = importRecord.moisLabel.split("/");
|
||||||
|
doc.setFontSize(10);
|
||||||
|
doc.text(`01/${moisStr}/${anneeStr}`, pageW / 2, 20, { align: "center" });
|
||||||
|
|
||||||
// Référence pièce
|
|
||||||
doc.setFontSize(8);
|
doc.setFontSize(8);
|
||||||
doc.setFont("helvetica", "bold");
|
doc.setFont("helvetica", "bold");
|
||||||
doc.text("ref_piece :", margin, 26);
|
doc.text("ref_piece :", margin, 27);
|
||||||
doc.setFont("helvetica", "normal");
|
doc.setFont("helvetica", "normal");
|
||||||
doc.text(importRecord.refPiece ?? "", margin + 22, 26);
|
doc.text(importRecord.refPiece ?? "", margin + 22, 27);
|
||||||
|
|
||||||
// Données tableau
|
// ── Calcul des dimensions du tableau ────────────────────────────────────────
|
||||||
const tableData = lines.map((l) => [
|
|
||||||
l.structure ?? "(vide)",
|
|
||||||
l.type,
|
|
||||||
formatMontantPdf(l.montantCentimes),
|
|
||||||
]);
|
|
||||||
const totalCentimes = lines.reduce((s, l) => s + l.montantCentimes, 0);
|
const totalCentimes = lines.reduce((s, l) => s + l.montantCentimes, 0);
|
||||||
|
// nbRows = 1 header + N lignes + 1 footer
|
||||||
// Largeurs colonnes A4 portrait (~182mm utiles)
|
|
||||||
const usableW = pageW - margin * 2;
|
|
||||||
const colStructure = usableW * 0.45;
|
|
||||||
const colType = usableW * 0.32;
|
|
||||||
const colMontant = usableW * 0.23;
|
|
||||||
|
|
||||||
// Calcul précis pour tenir sur 1 page :
|
|
||||||
// nbRows = lignes données + 1 header + 1 footer
|
|
||||||
const nbRows = lines.length + 2;
|
const nbRows = lines.length + 2;
|
||||||
// Hauteur disponible entre fin d'en-tête et bas de page
|
// Hauteur totale disponible pour le tableau
|
||||||
const availH = pageH - headerEndY - footerH;
|
const availH = tableEndY - tableStartY;
|
||||||
// Hauteur max par cellule (en mm) pour tenir en 1 page
|
// Hauteur exacte par ligne
|
||||||
const maxCellH = availH / nbRows;
|
const rowH = availH / nbRows;
|
||||||
// Padding : 1/4 de la hauteur de cellule, min 0.8, max 2.5
|
// Police : 1pt = 0.353mm, on prend 55% de rowH pour le texte
|
||||||
const cellPad = Math.max(0.8, Math.min(2.5, maxCellH * 0.25));
|
const fontSize = Math.max(5, Math.min(9, Math.floor(rowH * 0.55 / 0.353)));
|
||||||
// Font size : basé sur hauteur de cellule disponible pour le texte
|
|
||||||
const textH = maxCellH - cellPad * 2;
|
|
||||||
// 1pt ≈ 0.353mm ; on vise textH mm de hauteur de texte
|
|
||||||
const fontSize = Math.max(5, Math.min(9, Math.floor(textH / 0.353)));
|
|
||||||
|
|
||||||
autoTable(doc, {
|
// Largeurs colonnes
|
||||||
startY: headerEndY,
|
const col0W = usableW * 0.45; // Structure
|
||||||
head: [["Structure", "Type", "Montant TTC"]],
|
const col1W = usableW * 0.32; // Type
|
||||||
body: tableData,
|
const col2W = usableW * 0.23; // Montant
|
||||||
foot: [["Total general", "", formatMontantPdf(totalCentimes)]],
|
const col1X = margin + col0W;
|
||||||
styles: { fontSize, cellPadding: cellPad, overflow: "ellipsize", minCellHeight: 0 },
|
const col2X = col1X + col1W;
|
||||||
headStyles: {
|
|
||||||
fillColor: [255, 255, 255], textColor: [0, 0, 0],
|
// ── Dessin manuel du tableau ─────────────────────────────────────────────────
|
||||||
fontStyle: "bold", lineWidth: 0.3, lineColor: [0, 0, 0],
|
doc.setFontSize(fontSize);
|
||||||
minCellHeight: 0,
|
|
||||||
},
|
const drawRow = (y: number, c0: string, c1: string, c2: string, bold: boolean, bg?: [number, number, number]) => {
|
||||||
footStyles: {
|
// Fond de ligne
|
||||||
fillColor: [255, 255, 255], textColor: [0, 0, 0],
|
if (bg) {
|
||||||
fontStyle: "bold", lineWidth: 0.3, lineColor: [0, 0, 0],
|
doc.setFillColor(bg[0], bg[1], bg[2]);
|
||||||
minCellHeight: 0,
|
doc.rect(margin, y, usableW, rowH, "F");
|
||||||
},
|
}
|
||||||
bodyStyles: { lineWidth: 0.1, lineColor: [180, 180, 180], minCellHeight: 0 },
|
// Texte
|
||||||
columnStyles: {
|
doc.setFont("helvetica", bold ? "bold" : "normal");
|
||||||
0: { cellWidth: colStructure },
|
const textY = y + rowH * 0.65; // centrage vertical approximatif
|
||||||
1: { cellWidth: colType },
|
const pad = 1.5;
|
||||||
2: { cellWidth: colMontant, halign: "right" },
|
doc.text(c0, margin + pad, textY, { maxWidth: col0W - pad * 2 });
|
||||||
},
|
doc.text(c1, col1X + pad, textY, { maxWidth: col1W - pad * 2 });
|
||||||
alternateRowStyles: { fillColor: [248, 248, 248] },
|
doc.text(c2, col2X + col2W - pad, textY, { align: "right", maxWidth: col2W - pad * 2 });
|
||||||
// Ne pas autoriser de saut de page
|
};
|
||||||
pageBreak: "avoid",
|
|
||||||
rowPageBreak: "avoid",
|
const drawHLine = (y: number, lw: number, r: number, g: number, b: number) => {
|
||||||
showFoot: "lastPage",
|
doc.setDrawColor(r, g, b);
|
||||||
margin: { left: margin, right: margin, bottom: footerH },
|
doc.setLineWidth(lw);
|
||||||
tableWidth: usableW,
|
doc.line(margin, y, margin + usableW, y);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const drawVLines = (y: number, h: number) => {
|
||||||
|
doc.setDrawColor(180, 180, 180);
|
||||||
|
doc.setLineWidth(0.1);
|
||||||
|
doc.line(margin, y, margin, y + h);
|
||||||
|
doc.line(col1X, y, col1X, y + h);
|
||||||
|
doc.line(col2X, y, col2X, y + h);
|
||||||
|
doc.line(margin + usableW, y, margin + usableW, y + h);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Header
|
||||||
|
const headerY = tableStartY;
|
||||||
|
drawHLine(headerY, 0.4, 0, 0, 0);
|
||||||
|
drawRow(headerY, "Structure", "Type", "Montant TTC", true);
|
||||||
|
drawHLine(headerY + rowH, 0.4, 0, 0, 0);
|
||||||
|
drawVLines(headerY, rowH);
|
||||||
|
|
||||||
|
// Body
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const l = lines[i];
|
||||||
|
const y = headerY + rowH * (i + 1);
|
||||||
|
const bg: [number, number, number] | undefined = i % 2 === 1 ? [248, 248, 248] : undefined;
|
||||||
|
drawRow(y, l.structure ?? "(vide)", l.type, formatMontantPdf(l.montantCentimes), false, bg);
|
||||||
|
drawHLine(y + rowH, 0.1, 200, 200, 200);
|
||||||
|
drawVLines(y, rowH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer (Total)
|
||||||
|
const footerY = headerY + rowH * (lines.length + 1);
|
||||||
|
drawHLine(footerY, 0.4, 0, 0, 0);
|
||||||
|
drawRow(footerY, "Total general", "", formatMontantPdf(totalCentimes), true, [240, 240, 240]);
|
||||||
|
drawHLine(footerY + rowH, 0.4, 0, 0, 0);
|
||||||
|
drawVLines(footerY, rowH);
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user