first commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/logic/documents/builders/invoice.builder.js
|
||||
|
||||
module.exports = {
|
||||
sheetName: "Invoice",
|
||||
|
||||
build: async (worksheet, data) => {
|
||||
worksheet.addRow(["INVOICE"]).font = {
|
||||
bold: true,
|
||||
size: 18,
|
||||
};
|
||||
|
||||
worksheet.addRow([]);
|
||||
|
||||
worksheet.addRow(["Invoice No", data.invoiceNo]);
|
||||
worksheet.addRow(["Customer", data.customerName]);
|
||||
worksheet.addRow(["Date", data.date]);
|
||||
|
||||
worksheet.addRow([]);
|
||||
|
||||
const header = worksheet.addRow([
|
||||
"Description",
|
||||
"Qty",
|
||||
"Rate",
|
||||
"Amount",
|
||||
]);
|
||||
|
||||
header.font = { bold: true };
|
||||
|
||||
let total = 0;
|
||||
|
||||
(data.items || []).forEach((item) => {
|
||||
const amount = item.qty * item.rate;
|
||||
|
||||
total += amount;
|
||||
|
||||
worksheet.addRow([
|
||||
item.description,
|
||||
item.qty,
|
||||
item.rate,
|
||||
amount,
|
||||
]);
|
||||
});
|
||||
|
||||
worksheet.addRow([]);
|
||||
|
||||
worksheet.addRow([
|
||||
"",
|
||||
"",
|
||||
"TOTAL",
|
||||
total,
|
||||
]).font = {
|
||||
bold: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/logic/documents/builders/precost.builder.js
|
||||
|
||||
module.exports = {
|
||||
sheetName: "PreCost Estimate",
|
||||
|
||||
build: async (worksheet, data) => {
|
||||
worksheet.addRow(["PreCost Estimate"]).font = {
|
||||
bold: true,
|
||||
size: 16,
|
||||
};
|
||||
|
||||
worksheet.addRow([]);
|
||||
|
||||
worksheet.addRow([
|
||||
"Customer",
|
||||
data.customerName || "",
|
||||
]);
|
||||
|
||||
worksheet.addRow([]);
|
||||
|
||||
const header = worksheet.addRow([
|
||||
"Item",
|
||||
"Description",
|
||||
"Qty",
|
||||
"Unit Price",
|
||||
"Total",
|
||||
]);
|
||||
|
||||
header.font = { bold: true };
|
||||
|
||||
let total = 0;
|
||||
|
||||
(data.items || []).forEach((item) => {
|
||||
const rowTotal = item.quantity * item.unit_price;
|
||||
|
||||
total += rowTotal;
|
||||
|
||||
worksheet.addRow([
|
||||
item.item_name,
|
||||
item.customer_remark ,
|
||||
item.quantity,
|
||||
item.unit_price,
|
||||
rowTotal,
|
||||
]);
|
||||
});
|
||||
|
||||
worksheet.addRow([]);
|
||||
|
||||
worksheet.addRow([
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"Grand Total",
|
||||
total,
|
||||
]).font = {
|
||||
bold: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/logic/documents/engine/excel.engine.js
|
||||
|
||||
const ExcelJS = require("exceljs");
|
||||
|
||||
const generateExcel = async (builder, data) => {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet(builder.sheetName);
|
||||
|
||||
await builder.build(worksheet, data);
|
||||
|
||||
return await workbook.xlsx.writeBuffer();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generateExcel,
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/logic/documents/engine/pdf.engine.js
|
||||
|
||||
const puppeteer = require("puppeteer");
|
||||
|
||||
const generatePDF = async (html) => {
|
||||
const launchConfig = {
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||
};
|
||||
|
||||
if (process.env.PUPPETEER_EXECUTABLE_PATH) {
|
||||
launchConfig.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
||||
} else {
|
||||
launchConfig.headless = true;
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch(launchConfig);
|
||||
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.setContent(html, {
|
||||
waitUntil: "load",
|
||||
});
|
||||
|
||||
// Wait for fonts
|
||||
await page.evaluate(() => document.fonts?.ready);
|
||||
|
||||
// Wait for images to fully decode
|
||||
await page.evaluate(async () => {
|
||||
const images = Array.from(document.images);
|
||||
|
||||
await Promise.all(
|
||||
images.map((img) => {
|
||||
if (img.complete && img.naturalHeight !== 0) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
img.onerror = resolve;
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Force layout + paint
|
||||
await page.evaluate(() => document.body.getBoundingClientRect());
|
||||
|
||||
// Extra stability delay (important on servers)
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
|
||||
const buffer = await page.pdf({
|
||||
format: "A4",
|
||||
printBackground: true,
|
||||
});
|
||||
|
||||
return buffer;
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { generatePDF };
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/logic/documents/index.js
|
||||
|
||||
const registry = require("./registry");
|
||||
|
||||
const { generateExcel } = require("./engine/excel.engine");
|
||||
const {
|
||||
generatePdfFromPreCost,
|
||||
generatePdfFromInvoice,
|
||||
generatePdfFromDispatchNote,
|
||||
generatePdfFromDeliveryNote,
|
||||
generatePdfFromPO,
|
||||
generatePdfFromCustomDocument,
|
||||
generatePdfFromCreditNote,
|
||||
} = require("../../utils/pdfGenerator");
|
||||
|
||||
const formatMeta = {
|
||||
pdf: {
|
||||
ext: "pdf",
|
||||
mime: "application/pdf",
|
||||
},
|
||||
|
||||
excel: {
|
||||
ext: "xlsx",
|
||||
mime:
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
},
|
||||
};
|
||||
|
||||
const pdfGenerators = {
|
||||
precost: generatePdfFromPreCost,
|
||||
invoice: generatePdfFromInvoice,
|
||||
dispatchnote: generatePdfFromDispatchNote,
|
||||
deliverynote: generatePdfFromDeliveryNote,
|
||||
po: generatePdfFromPO,
|
||||
customdocument: generatePdfFromCustomDocument,
|
||||
creditnote: generatePdfFromCreditNote,
|
||||
};
|
||||
|
||||
const generateDocument = async (
|
||||
document,
|
||||
format,
|
||||
data
|
||||
) => {
|
||||
document = document.toLowerCase().replace(/[\s_-]+/g, "");
|
||||
format = format.toLowerCase();
|
||||
|
||||
const doc = registry[document];
|
||||
|
||||
if (!doc) {
|
||||
const availableTypes = Object.keys(registry).join(", ");
|
||||
throw new Error(`Unsupported document type: "${document}". Available types: ${availableTypes}`);
|
||||
}
|
||||
|
||||
let fileBuffer;
|
||||
|
||||
if (format === "pdf") {
|
||||
// Format the data using the template
|
||||
const formattedData = await doc.pdfTemplate(data);
|
||||
|
||||
// Get the correct PDF generator for this document type
|
||||
const pdfGenerator = pdfGenerators[document];
|
||||
|
||||
if (pdfGenerator) {
|
||||
fileBuffer = await pdfGenerator(formattedData);
|
||||
} else {
|
||||
throw new Error(`No PDF generator found for document type: ${document}`);
|
||||
}
|
||||
}
|
||||
|
||||
else if (format === "excel") {
|
||||
if (doc.excelBuilder) {
|
||||
fileBuffer = await generateExcel(
|
||||
doc.excelBuilder,
|
||||
data
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Excel generation not supported for document type: ${document}`);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
throw new Error("Unsupported format");
|
||||
}
|
||||
|
||||
const meta = formatMeta[format];
|
||||
|
||||
return {
|
||||
fileBuffer,
|
||||
fileName: `${document}-${Date.now()}.${meta.ext}`,
|
||||
mimeType: meta.mime,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generateDocument,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/logic/documents/registry.js
|
||||
|
||||
let buildPreCostTemplate, buildInvoiceTemplate, buildDispatchNoteTemplate, buildDeliveryNoteTemplate, buildPOTemplate, buildCustomDocumentTemplate, buildCreditNoteTemplate;
|
||||
let precostBuilder, invoiceBuilder;
|
||||
|
||||
try {
|
||||
buildPreCostTemplate = require("../../templates/documents/precost/preCost.template");
|
||||
buildInvoiceTemplate = require("../../templates/documents/invoice/invoice.template");
|
||||
buildDispatchNoteTemplate = require("../../templates/documents/dispatchNote/dispatchNote.template");
|
||||
buildDeliveryNoteTemplate = require("../../templates/documents/deliveryNote/deliveryNote.template");
|
||||
buildPOTemplate = require("../../templates/documents/po/po.template");
|
||||
buildCustomDocumentTemplate = require("../../templates/documents/customDocument/customDocument.template");
|
||||
buildCreditNoteTemplate = require("../../templates/documents/creditNote/creditNote.template");
|
||||
|
||||
precostBuilder = require("./builders/precost.builder");
|
||||
invoiceBuilder = require("./builders/invoice.builder");
|
||||
|
||||
console.log("✅ All document templates loaded successfully");
|
||||
} catch (error) {
|
||||
console.error("❌ Error loading document templates:", error.message);
|
||||
console.error(error.stack);
|
||||
}
|
||||
|
||||
const registry = {
|
||||
precost: {
|
||||
pdfTemplate: buildPreCostTemplate,
|
||||
excelBuilder: precostBuilder,
|
||||
},
|
||||
|
||||
invoice: {
|
||||
pdfTemplate: buildInvoiceTemplate,
|
||||
excelBuilder: invoiceBuilder,
|
||||
},
|
||||
|
||||
dispatchnote: {
|
||||
pdfTemplate: buildDispatchNoteTemplate,
|
||||
excelBuilder: null,
|
||||
},
|
||||
|
||||
deliverynote: {
|
||||
pdfTemplate: buildDeliveryNoteTemplate,
|
||||
excelBuilder: null,
|
||||
},
|
||||
|
||||
po: {
|
||||
pdfTemplate: buildPOTemplate,
|
||||
excelBuilder: null,
|
||||
},
|
||||
|
||||
customdocument: {
|
||||
pdfTemplate: buildCustomDocumentTemplate,
|
||||
excelBuilder: null,
|
||||
},
|
||||
|
||||
creditnote: {
|
||||
pdfTemplate: buildCreditNoteTemplate,
|
||||
excelBuilder: null,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("📋 Registry initialized with keys:", Object.keys(registry));
|
||||
|
||||
module.exports = registry;
|
||||
Reference in New Issue
Block a user