Untitled
unknown
plain_text
9 months ago
12 kB
7
Indexable
import PDFDocument from 'pdfkit';
import fs from 'fs';
interface Invoice {
invoice?: string;
billTo?: string;
billToInfo?: any;
shipTo?: string;
shipToInfo?: any;
paymentDate?: string;
paymentDateInfo?: string;
paymentTerms?: string;
paymentTermsInfo?: string;
dueDate?: string;
dueDateInfo?: string;
poNumber?: string;
poNumberInfo?: string;
amountDue?: string;
calc?: {
due?: number;
subTotal?: number;
};
headerBgColor?: string;
headerTextColor?: string;
item?: string;
quantity?: string;
rate?: string;
amount?: string;
rows?: Array<{
title: string;
quantity: number;
rate: number;
}>;
subtotal?: string;
shipping?: string;
shippingInfo?: number;
tax?: string;
taxInfo?: number;
discount?: string;
discountInfo?: number;
total?: string;
totalInfo?: number;
amountPaid?: string;
amountPaidInfo?: number;
note?: string;
noteInfo?: string;
terms?: string;
termsInfo?: string;
}
function createInvoice(invoice: Invoice, outputPath: string): void {
if (!invoice || typeof invoice !== 'object') {
throw new Error('Invalid input invoice provided.');
}
const doc = new PDFDocument({ margin: 35, size: 'A4' });
doc.pipe(fs.createWriteStream(outputPath));
doc.font('Helvetica');
generateHeader(doc, invoice);
const dynamicSectionsEndY = generateDynamicSections(doc, invoice, 50, 140);
const tableStartY = Math.max(dynamicSectionsEndY, 300);
generateInvoiceDetails(doc, invoice);
generateInvoiceTable(doc, invoice, tableStartY);
doc.end();
}
function generateHeader(doc: PDFKit.PDFDocument, invoice: Invoice): void {
doc.image('bg-new.png', 0, 0, { width: 600, fit: [100, 100] });
doc.image('invoice footer-bg.png', 0, 815, { width: 600, fit: [100, 100] });
doc
.fillColor('#000000')
.fontSize(10)
.fontSize(12)
.text(`${invoice?.invoice}`, 20, 86, { align: 'right' })
.fontSize(14)
.moveDown();
}
function generateDynamicSections(doc: PDFKit.PDFDocument, data: Invoice, startX: number, startY: number): number {
const sectionSpacing = 152;
let currentX = startX;
let maxY = startY;
const extraSpacing = 4;
const flattenObject = (obj: any, parentKey = ''): any => {
return Object.keys(obj).reduce((acc, key) => {
const newKey = parentKey ? `${parentKey}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(acc, flattenObject(obj[key], newKey));
} else {
acc[newKey] = obj[key];
}
return acc;
}, {});
};
const billToInfo = flattenObject(data.billToInfo);
let currentY = startY;
doc
.fillColor('#666666')
.font('Helvetica')
.fontSize(11)
.text(`${data.billTo}`, currentX - 20, currentY);
currentY += 15;
Object.keys(billToInfo).forEach((key) => {
const value = billToInfo[key];
const wrappedText = `${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`;
doc
.fillColor('#333333')
.font('Helvetica')
.fontSize(10)
.text(wrappedText, currentX - 20, currentY, {
width: 160,
lineBreak: true,
});
const textHeight = doc.heightOfString(wrappedText, { width: 160 });
currentY += textHeight + extraSpacing;
});
maxY = Math.max(maxY, currentY);
currentX += sectionSpacing;
const shipToInfo = flattenObject(data.shipToInfo);
currentY = startY;
doc
.fillColor('#666666')
.font('Helvetica')
.fontSize(11)
.text(`${data.shipTo}`, currentX, currentY);
currentY += 15;
Object.keys(shipToInfo).forEach((key) => {
const value = shipToInfo[key];
const wrappedText = `${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`;
doc
.fillColor('#333333')
.font('Helvetica')
.fontSize(10)
.text(wrappedText, currentX, currentY, {
width: 160,
lineBreak: true,
});
const textHeight = doc.heightOfString(wrappedText, { width: 160 });
currentY += textHeight + extraSpacing;
});
maxY = Math.max(maxY, currentY);
return maxY;
}
function generateInvoiceDetails(doc: PDFKit.PDFDocument, invoice: Invoice): void {
const detailsTop = 130;
let currentY = detailsTop;
const isEmpty = (value: any): boolean => {
return value === null || value === undefined || value === '';
};
if (!isEmpty(invoice?.paymentDate) && !isEmpty(invoice.paymentDateInfo)) {
doc
.fontSize(10)
.fillColor('#666666')
.text(`${invoice.paymentDate}`, 0, currentY, {
width: 470,
align: 'right',
})
.fillColor('#000000')
.text(formatDate(new Date(invoice.paymentDateInfo)), 0, currentY, {
width: 550,
align: 'right',
});
currentY += 25;
}
if (!isEmpty(invoice?.paymentTerms) && !isEmpty(invoice.paymentTermsInfo)) {
doc
.fontSize(10)
.fillColor('#666666')
.text(`${invoice.paymentTerms}`, 0, currentY, {
width: 470,
align: 'right',
})
.fillColor('#000000')
.text(`${invoice.paymentTermsInfo}`, 0, currentY, {
width: 550,
align: 'right',
});
currentY += 25;
}
if (!isEmpty(invoice?.dueDate) && !isEmpty(invoice.dueDateInfo)) {
doc
.fontSize(10)
.fillColor('#666666')
.text(`${invoice.dueDate}`, 0, currentY, {
width: 470,
align: 'right',
})
.fillColor('#000000')
.text(formatDate(new Date(invoice.dueDateInfo)), 0, currentY, {
width: 550,
align: 'right',
});
currentY += 25;
}
if (!isEmpty(invoice?.poNumber) && !isEmpty(invoice.poNumberInfo)) {
doc
.fontSize(10)
.fillColor('#666666')
.text(`${invoice.poNumber}`, 0, currentY, {
width: 470,
align: 'right',
})
.fillColor('#000000')
.text(`${invoice.poNumberInfo}`, 0, currentY, {
width: 550,
align: 'right',
});
currentY += 25;
}
let stockY = 198.5;
const rectWidth = 185;
const rectHeight = 20;
const roundingRadius = 2;
doc.roundedRect(390, stockY, rectWidth, rectHeight, roundingRadius);
doc.fill(invoice.headerBgColor?.slice(0, 7));
doc.font('Helvetica-Bold');
doc.fillColor(`${invoice.headerTextColor.slice(0, 7)}`);
if (!isEmpty(invoice?.amountDue) && !isEmpty(invoice.calc?.due)) {
doc
.font('Helvetica-Bold')
.text(`${invoice.amountDue}`, 0, currentY, {
width: 470,
align: 'right',
})
.text(`${invoice.calc.due}`, 0, currentY, {
width: 550,
align: 'right',
});
}
}
function generateInvoiceTable(doc: PDFKit.PDFDocument, invoice: Invoice, tableStartY: number): void {
let i;
const invoiceTableTop = 320;
let currentY = 308.5;
const rectWidth = 555;
const rectHeight = 20;
const roundingRadius = 2;
doc
.roundedRect(20, currentY, rectWidth, rectHeight, roundingRadius)
.fill(`${invoice.headerBgColor}`);
doc.font('Helvetica-Bold');
doc.fillColor(`${invoice.headerTextColor}`);
generateTableRow(
doc,
invoiceTableTop - 5,
`${invoice.item}`,
`${invoice.quantity}`,
`${invoice.rate}`,
`${invoice.amount}`
);
doc.fillColor('#000000');
doc.font('Helvetica');
for (i = 0; i < invoice.rows!.length; i++) {
const item = invoice.rows![i];
const position = invoiceTableTop + (i + 1) * 24;
if (position > doc.page.height - 50) {
doc.addPage();
currentY = 50;
}
generateTableRow(
doc,
position,
item.title,
item.quantity,
item.rate,
item.quantity * item.rate,
'center'
);
}
const subtotalPosition = invoiceTableTop + (i + 1) * 27;
generateHr(doc, subtotalPosition - 15);
generateTableInfo(
doc,
subtotalPosition,
'',
'',
`${invoice.subtotal}`,
formatCurrency(invoice.calc?.subTotal || 0)
);
let currentPosition = subtotalPosition + 20;
if (invoice.shippingInfo) {
generateTableInfo(
doc,
currentPosition,
'',
'',
`${invoice.shipping}`,
formatCurrency(invoice.shippingInfo)
);
currentPosition += 20;
}
if (invoice.taxInfo) {
generateTableInfo(
doc,
currentPosition,
'',
'',
`${invoice.tax}`,
formatCurrency(invoice.taxInfo)
);
currentPosition += 20;
}
if (invoice.discountInfo) {
generateTableInfo(
doc,
currentPosition,
'',
'',
`${invoice.discount}`,
formatCurrency(invoice.discountInfo)
);
currentPosition += 20;
}
if (invoice.totalInfo) {
generateTableInfo(
doc,
currentPosition,
'',
'',
`${invoice.total}`,
formatCurrency(invoice.totalInfo)
);
currentPosition += 20;
}
if (invoice.amountPaidInfo) {
generateTableInfo(
doc,
currentPosition,
'',
'',
`${invoice.amountPaid}`,
formatCurrency(invoice.amountPaidInfo)
);
currentPosition += 20;
}
const duePosition = currentPosition;
doc.font('Helvetica-Bold');
generateTableInfo(
doc,
duePosition,
'',
'',
`${invoice.amountDue}`,
formatCurrency(invoice.calc?.due || 0)
);
const tableHeight = duePosition + 20;
generateNotesAndTerms(doc, invoice, tableHeight);
}
function generateTableRow(
doc: PDFKit.PDFDocument,
y: number,
item: string,
unitCost: string,
quantity: string,
lineTotal: string,
align: string = 'right'
): void {
doc
.fontSize(10)
.text(item, 30, y)
.text(unitCost, 320, y, { width: 90, align: 'center' })
.text(quantity, 420, y, { width: 90, align: 'center' })
.text(lineTotal, 470, y, { align: 'right' });
}
function generateTableInfo(
doc: PDFKit.PDFDocument,
y: number,
item: string,
unitCost: string,
quantity: string,
lineTotal: string
): void {
doc
.fontSize(10)
.text(item, 30, y)
.text(unitCost, 240, y, { width: 90, align: 'right' })
.text(quantity, 360, y, { width: 90, align: 'right' })
.text(lineTotal, 470, y, { align: 'right' });
}
function generateNotesAndTerms(doc: PDFKit.PDFDocument, invoice: Invoice, tableHeight: number): void {
let notePosition = tableHeight + 20;
const lorem1Height = doc.heightOfString(`${invoice?.noteInfo}`, {
width: 500,
});
if (invoice.noteInfo) {
doc.fontSize(10).text(`${invoice.note}`, 25, notePosition);
doc.font('Helvetica').text(`${invoice.noteInfo}`, 25, notePosition + 15);
}
const termsPosition = notePosition + lorem1Height + 20;
if (invoice.termsInfo) {
doc
.fontSize(10)
.font('Helvetica-Bold')
.text(`${invoice.terms}`, 25, termsPosition);
doc.font('Helvetica').text(`${invoice.termsInfo}`, 25, termsPosition + 15);
}
}
function formatCurrency(amount: number): string {
return `$${Number(amount)?.toFixed(2)}`;
}
function generateHr(doc: PDFKit.PDFDocument, y: number): void {
doc
.strokeColor('#06243E')
.lineWidth(0.1)
.moveTo(27, y)
.lineTo(562, y)
.stroke();
}
function formatDate(date: Date): string {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
return `${year}/${month}/${day}`;
}
export { createInvoice };Editor is loading...
Leave a Comment