Untitled
unknown
typescript
21 days ago
15 kB
3
Indexable
import fs from "fs"; import path from "path"; import PDFDocument from "pdfkit"; interface Invoice { invoice?: string; billTo?: string; billToInfo?: any; shipTo?: string; shipToInfo?: any; paymentDate?: string; paymentDateInfo?: string; paymentTerms?: string; calculationModes: any; paymentTermsInfo?: string; currency?: string; dueDate?: string; dueDateInfo?: string; poNumber?: string; poNumberInfo?: string; amountDue?: string; calc?: { due?: number; subTotal?: number; grandTotal: any; }; 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; waterMarkOpacity: number; discount?: string; discountInfo?: number; total?: string; totalInfo?: number; amountPaid?: string; amountPaidInfo?: number; note?: string; noteInfo?: string; terms?: string; termsInfo?: string; invoiceType: "default" | "custom"; logo?: string; header?: string; footer?: string; watermark?: string; invoiceNum?: string; status: string; } async function createCustomInvoice( invoice: Invoice, outputPath: string ): Promise<void> { if (!invoice || typeof invoice !== "object") { throw new Error("Invalid input invoice provided."); } let mInvoice = { ...invoice }; ["user", "createdAt", "updatedAt", "id", "type"].forEach((key) => { delete mInvoice?.billToInfo?.[key]; delete mInvoice?.shipToInfo?.[key]; }); 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 { if (invoice?.header) { doc.image( path.join(process.cwd(), "files", "images", invoice.header), 0, 0, { width: 600, height: 80 } ); } if (invoice?.footer) doc.image( path.join(process.cwd(), "files", "images", invoice.footer), 0, 805, { width: 600, fit: [100, 100] } ); if (invoice?.watermark) doc.opacity(Number(invoice?.waterMarkOpacity) / 100 || 1); if (invoice?.watermark) { doc.image( path.join(process.cwd(), "files", "images", invoice?.watermark || ""), 163, 290, { width: 270, fit: [100, 100] } ); } doc.opacity(100 / 100); // if (invoice?.invoice) { // doc // .fillColor("#000000") // .fontSize(24) // .text(`${invoice?.invoice}`, 0, 75, { align: "right" }) // .moveDown(); // } // if (invoice?.invoiceNum) { // doc // .fillColor("#000000") // .fontSize(12) // .text(`${invoice?.invoiceNum}`, 0, 100, { 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 = 6; 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 { // @ts-ignore 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( formatDateDDMMYYYY(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(formatDateDDMMYYYY(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 = currentY - 6.1; const rectWidth = 185; const rectHeight = 20; const roundingRadius = 2; doc.roundedRect(390, stockY, rectWidth, rectHeight, roundingRadius); doc.fill(invoice.headerBgColor); doc.font("Helvetica-Bold"); doc.fillColor(`${invoice?.headerTextColor}`); if (!isEmpty(invoice?.amountDue) && !isEmpty(invoice.calc?.due)) { doc .font("Helvetica-Bold") .text(`${invoice?.subtotal}`, 0, currentY, { width: 470, align: "right", }) .text(`${invoice?.currency} ${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.toString(), `${invoice.currency} ${item.rate.toString()}`, `${invoice.currency} ${item.quantity * item.rate}`.toString(), "center" ); } const subtotalPosition = invoiceTableTop + (i + 1) * 27; doc.opacity(30 / 100); generateHr(doc, subtotalPosition - 15); doc.opacity(100 / 100); generateTableInfo( doc, subtotalPosition, "", "", `${invoice.subtotal}`, formatCurrency(invoice.calc?.subTotal || 0, invoice?.currency || "$") ); let currentPosition = subtotalPosition + 20; if (invoice.shippingInfo) { generateTableInfo( doc, currentPosition, "", "", `${invoice.shipping} (${invoice.shippingInfo} ${invoice.calculationModes.shipping})`, formatCurrency(invoice.shippingInfo || 0, invoice?.currency || "$") ); currentPosition += 20; } if (invoice.taxInfo) { generateTableInfo( doc, currentPosition, "", "", `${invoice.tax} (${invoice.taxInfo} ${invoice.calculationModes.tax})`, formatCurrency(invoice.taxInfo || 0, invoice?.currency || "$") ); currentPosition += 20; } if (invoice.discountInfo) { generateTableInfo( doc, currentPosition, "", "", `${invoice.discount} (${invoice.discountInfo} ${invoice.calculationModes.discount})`, formatCurrency(invoice.discountInfo || 0, invoice?.currency || "$") ); currentPosition += 20; } if (invoice.calc?.grandTotal) { generateTableInfo( doc, currentPosition, "", "", `${invoice.total}`, formatCurrency(invoice.calc?.grandTotal || 0, invoice?.currency || "$") ); currentPosition += 20; } if (invoice.amountPaidInfo) { generateTableInfo( doc, currentPosition, "", "", `${invoice.amountPaid}`, formatCurrency( invoice.status === "PAID" ? invoice.calc?.grandTotal : invoice.amountPaidInfo || 0, invoice?.currency || "$" ) ); currentPosition += 20; } const duePosition = currentPosition; doc.font("Helvetica-Bold"); generateTableInfo( doc, duePosition, "", "", `${invoice.amountDue}`, formatCurrency( invoice.status === "PAID" ? 0 : invoice.calc?.due || 0, invoice?.currency || "$" ) ); const tableHeight = duePosition + 20; if (invoice.status === "PAID") { doc.opacity(50 / 100 || 1); doc.image("static/paid.png", 460, tableHeight + 40, { fit: [150, 60], }); } doc.opacity(100 / 100 || 1); 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; // Calculate the height required for the note and noteInfo const noteHeight = doc.heightOfString(`${invoice?.noteInfo}`, { width: 500, }); // Check if there is enough space on the current page for the note and noteInfo if (notePosition + noteHeight + 50 > doc.page.height) { doc.addPage(); notePosition = 50; // Reset the position to the top of the new page } if (invoice.noteInfo) { doc.fontSize(10).text(`${invoice.note}`, 25, notePosition); doc.font("Helvetica").text(`${invoice.noteInfo}`, 25, notePosition + 15); } let termsPosition = notePosition + noteHeight + 20; // Check if there is enough space on the current page for the terms and termsInfo if (termsPosition + 50 > doc.page.height) { doc.addPage(); termsPosition = 50; // Reset the position to the top of the new page } 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, currency?: string): string { return `${currency === "" ? "" : currency} ${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}`; // } function formatDateDDMMYYYY(date: Date): string { const day = String(date.getDate()).padStart(2, "0"); const month = String(date.getMonth() + 1).padStart(2, "0"); const year = date.getFullYear(); return `${month}/${day}/${year}`; } export { createCustomInvoice };
Editor is loading...
Leave a Comment