197 lines
4.7 KiB
JavaScript
197 lines
4.7 KiB
JavaScript
/**
|
|
* Copyright (c) 2026 Niolla
|
|
* All rights reserved.
|
|
*/
|
|
|
|
// app/utils/pdfGenerator.js
|
|
|
|
const path = require("path");
|
|
const ejs = require("ejs");
|
|
const puppeteer = require("puppeteer");
|
|
const fs = require("fs");
|
|
const { imageToBase64 } = require("./imageToBase64.utill");
|
|
const { log, runningEnvironment } = require("./consoleLog.utill");
|
|
|
|
let browser;
|
|
|
|
let executablePath = null;
|
|
|
|
const isProd = runningEnvironment();
|
|
|
|
if (isProd) {
|
|
executablePath = "/usr/bin/chromium";
|
|
} else {
|
|
executablePath = puppeteer.executablePath();
|
|
}
|
|
|
|
|
|
/**
|
|
* Launch or reuse Puppeteer browser safely
|
|
*/
|
|
async function getBrowser() {
|
|
if (!browser || !browser.isConnected?.()) {
|
|
console.log("🚀 Launching Chromium from Docker system path...");
|
|
|
|
browser = await puppeteer.launch({
|
|
headless: "new",
|
|
executablePath: executablePath,
|
|
args: [
|
|
"--no-sandbox",
|
|
"--disable-setuid-sandbox",
|
|
"--disable-dev-shm-usage",
|
|
"--disable-gpu",
|
|
"--no-zygote"
|
|
],
|
|
});
|
|
|
|
console.log("✅ Browser launched");
|
|
}
|
|
|
|
return browser;
|
|
}
|
|
|
|
/**
|
|
* Close browser safely
|
|
*/
|
|
async function closeBrowser() {
|
|
if (browser) {
|
|
try {
|
|
await browser.close();
|
|
} catch (e) {}
|
|
browser = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Core PDF renderer
|
|
*/
|
|
async function renderPdf(templateFile, data = {}, opts = {}) {
|
|
const templatePath = path.join(__dirname, "../templates", templateFile);
|
|
|
|
console.log("📝 Rendering:", templateFile);
|
|
|
|
// Convert image to base64 if needed
|
|
if (data.logoUrl && !data.logoBase64) {
|
|
try {
|
|
data.logoBase64 = await imageToBase64(data.logoUrl);
|
|
} catch (err) {
|
|
console.error("❌ Logo conversion failed:", err.message);
|
|
data.logoBase64 = null;
|
|
}
|
|
}
|
|
|
|
const html = await ejs.renderFile(templatePath, data);
|
|
|
|
const browserInstance = await getBrowser();
|
|
const page = await browserInstance.newPage();
|
|
|
|
try {
|
|
// Safety timeouts
|
|
await page.setDefaultTimeout(60000);
|
|
await page.setDefaultNavigationTimeout(60000);
|
|
|
|
await page.setViewport({ width: 1200, height: 800 });
|
|
|
|
console.log("📄 Loading HTML...");
|
|
await page.setContent(html, { waitUntil: "networkidle0" });
|
|
|
|
await page.emulateMediaType("print");
|
|
|
|
// Wait for fonts
|
|
await page.evaluate(() => document.fonts?.ready || Promise.resolve());
|
|
|
|
const margin = {
|
|
top: 20,
|
|
bottom: 20,
|
|
left: 10,
|
|
right: 10,
|
|
...opts.margin,
|
|
};
|
|
|
|
// Optional: measure content (kept for logging only)
|
|
const contentHeightPx = await page.evaluate(() => {
|
|
const body = document.body;
|
|
const htmlEl = document.documentElement;
|
|
|
|
return Math.max(
|
|
body.scrollHeight,
|
|
body.offsetHeight,
|
|
htmlEl.scrollHeight,
|
|
htmlEl.offsetHeight
|
|
);
|
|
});
|
|
|
|
console.log("📏 Content height:", contentHeightPx, "px");
|
|
|
|
// ❗ REMOVED scaling logic (causes PDF distortion in production)
|
|
|
|
const showPageNumbers = opts.showPageNumbers === true;
|
|
|
|
console.log("⚙️ Generating PDF...");
|
|
|
|
const pdfBuffer = await page.pdf({
|
|
format: "A4",
|
|
printBackground: true,
|
|
preferCSSPageSize: true,
|
|
margin: {
|
|
top: `${margin.top}mm`,
|
|
bottom: `${margin.bottom}mm`,
|
|
left: `${margin.left}mm`,
|
|
right: `${margin.right}mm`,
|
|
},
|
|
displayHeaderFooter: showPageNumbers,
|
|
headerTemplate: `<div></div>`,
|
|
footerTemplate: showPageNumbers
|
|
? `
|
|
<div style="font-size:10px;width:100%;text-align:center;">
|
|
Page <span class="pageNumber"></span> / <span class="totalPages"></span>
|
|
</div>`
|
|
: `<div></div>`,
|
|
});
|
|
|
|
console.log("✅ PDF generated");
|
|
|
|
return pdfBuffer;
|
|
} catch (err) {
|
|
console.error("❌ PDF generation error:", err.message);
|
|
throw err;
|
|
} finally {
|
|
await page.close().catch(() => {});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helpers
|
|
*/
|
|
const generatePdfFromPreCost = (data) =>
|
|
renderPdf("documents/precost/precost.ejs", data);
|
|
|
|
const generatePdfFromInvoice = (data) =>
|
|
renderPdf("documents/invoice/invoice.ejs", data);
|
|
|
|
const generatePdfFromDispatchNote = (data) =>
|
|
renderPdf("documents/dispatchNote/dispatchNote.ejs", data);
|
|
|
|
const generatePdfFromDeliveryNote = (data) =>
|
|
renderPdf("documents/deliveryNote/deliveryNote.ejs", data);
|
|
|
|
const generatePdfFromPO = (data) =>
|
|
renderPdf("documents/po/po.ejs", data);
|
|
|
|
const generatePdfFromCustomDocument = (data) =>
|
|
renderPdf("documents/customDocument/customDocument.ejs", data);
|
|
|
|
const generatePdfFromCreditNote = (data) =>
|
|
renderPdf("documents/creditNote/creditNote.ejs", data);
|
|
|
|
module.exports = {
|
|
generatePdfFromPreCost,
|
|
generatePdfFromInvoice,
|
|
generatePdfFromDispatchNote,
|
|
generatePdfFromDeliveryNote,
|
|
generatePdfFromPO,
|
|
generatePdfFromCustomDocument,
|
|
generatePdfFromCreditNote,
|
|
renderPdf,
|
|
closeBrowser,
|
|
}; |