Receipt Generator

Receipt Generator

Business & Transaction Details

Used for calculating the tax amount.

How was the payment settled?

Add Line Items

Current Items (Dashboard)

Description Unit Price ($) Qty Line Total ($) Actions

Final calculated receipt preview.

Please review Tab 2 and click "Next" or "Previous" to refresh this preview.

Payment: ${escapeHTML(data.paymentMethod)}

Date: ${escapeHTML(data.date)}

Tax Rate: ${data.totals.taxRate.toFixed(1)}%

${itemRowsHTML.length > 0 ? itemRowsHTML : ''}
Item Description Price Qty Total
No items logged.
Subtotal: ${formatCurrency(data.totals.subtotal)}
Sales Tax (${data.totals.taxRate.toFixed(1)}%): ${formatCurrency(data.totals.taxAmount)}
GRAND TOTAL: ${formatCurrency(data.totals.grandTotal)}

${escapeHTML(data.memo)}

`; }; const downloadTxt = () => { const data = getReceiptData(); const totals = data.totals; let content = `\t\t\t\tOFFICIAL RECEIPT\n`; content += `\t\t\t\t${data.company.toUpperCase()}\n`; content += "========================================================\n"; content += `Date: ${data.date}\n`; content += `Customer: ${data.customer}\n`; content += `Payment: ${data.paymentMethod}\n`; content += "--------------------------------------------------------\n"; content += "Description\t\t\tPrice\tQty\tLine Total\n"; content += "--------------------------------------------------------\n"; data.items.forEach(item => { const lineTotal = item.price * item.quantity; content += `${item.description.padEnd(28).substring(0, 28)}\t${item.price.toFixed(2)}\t${item.quantity}\t${lineTotal.toFixed(2)}\n`; }); content += "--------------------------------------------------------\n"; content += `\t\t\t\tSubtotal:\t${totals.subtotal.toFixed(2)}\n`; content += `\t\t\t\tTax (${totals.taxRate.toFixed(1)}%):\t${totals.taxAmount.toFixed(2)}\n`; content += `\t\t\t\tGRAND TOTAL:\t${totals.grandTotal.toFixed(2)}\n`; content += "\n========================================================\n"; content += `\t\t${data.memo}\n`; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `receipt_${data.company.replace(/ /g, '_')}_${data.date}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'mm', 'a4'); const data = getReceiptData(); const totals = data.totals; const margin = 20; const pageWidth = doc.internal.pageSize.getWidth(); let yPos = 20; // 1. Header doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(41, 128, 185); doc.text(data.company, pageWidth / 2, yPos, { align: 'center' }); yPos += 8; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(108, 117, 125); doc.text("OFFICIAL RECEIPT", pageWidth / 2, yPos, { align: 'center' }); yPos += 10; // 2. Metadata (using a mini table for clean layout) const metadata = [ ['Customer Name', data.customer, 'Date', data.date], ['Payment Method', data.paymentMethod, 'Tax Rate', `${totals.taxRate.toFixed(1)}%`] ]; doc.autoTable({ startY: yPos, body: metadata, theme: 'plain', styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94] }, columnStyles: { 0: { fontStyle: 'bold', cellWidth: 30 }, 2: { fontStyle: 'bold', cellWidth: 30 } }, margin: { left: margin, right: margin } }); // FIX: Defensive Y-position update after autoTable call yPos = doc.autoTable.previous ? (doc.autoTable.previous.finalY + 5) : (yPos + 20); // 3. Line Items Table const itemHead = [['Description', 'Unit Price', 'Qty', 'Line Total']]; const itemBody = data.items.map(item => [ item.description, formatCurrency(item.price), item.quantity, formatCurrency(item.price * item.quantity) ]); doc.autoTable({ startY: yPos, head: itemHead, body: itemBody, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94] }, headStyles: { fillColor: [233, 236, 239], textColor: [44, 62, 80], fontStyle: 'bold', halign: 'right' }, columnStyles: { 0: { halign: 'left', fontStyle: 'normal' }, 1: { halign: 'right' }, 2: { halign: 'right' }, 3: { halign: 'right', fontStyle: 'bold' } }, margin: { left: margin, right: margin } }); // FIX: Defensive Y-position update after autoTable call yPos = doc.autoTable.previous ? (doc.autoTable.previous.finalY + 5) : (yPos + 20); // 4. Totals Box const totalsX = pageWidth - margin - 50; // Start X for totals column const addTotalLine = (label, value, isGrand = false) => { doc.setFontSize(isGrand ? 12 : 10); doc.setFont(undefined, isGrand ? 'bold' : 'normal'); doc.setTextColor(isGrand ? 41 : 52, isGrand ? 128 : 73, isGrand ? 185 : 94); if (isGrand) { doc.setDrawColor(44, 62, 80); doc.setLineWidth(0.5); doc.line(totalsX - 10, yPos - 1, pageWidth - margin, yPos - 1); yPos += 2; } doc.text(label, totalsX, yPos, { align: 'left' }); doc.text(formatCurrency(value), pageWidth - margin, yPos, { align: 'right' }); yPos += 5; }; yPos += 5; addTotalLine("Subtotal:", totals.subtotal); addTotalLine(`Tax (${totals.taxRate.toFixed(1)}%):`, totals.taxAmount); yPos += 2; addTotalLine("GRAND TOTAL:", totals.grandTotal, true); yPos += 10; // 5. Memo / Footer doc.setFontSize(9); doc.setFont(undefined, 'italic'); doc.setTextColor(108, 117, 125); const splitMemo = doc.splitTextToSize(data.memo, pageWidth - margin * 2); doc.text(splitMemo, pageWidth / 2, yPos, { align: 'center' }); doc.save(`receipt_${data.company.replace(/ /g, '_')}_${data.date}.pdf`); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Add, Remove, Edit) itemInputs.addBtn.addEventListener('click', addItem); itemInputs.tbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeItem(parseInt(e.target.dataset.removeId)); } }); itemInputs.tbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateItem(id, field, e.target.textContent); } }, true); // Tab 3 Actions downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- // Add sample data lineItems.push({ id: itemIdCounter++, description: "Premium Web Hosting - 1 Year", price: 199.99, quantity: 1 }); lineItems.push({ id: itemIdCounter++, description: "Domain Registration (.com)", price: 14.99, quantity: 1 }); lineItems.push({ id: itemIdCounter++, description: "Tech Support Hourly Rate", price: 75.00, quantity: 2 }); showTab(1); // Set initial state });
Scroll to Top