// Receipt Designer Application class ReceiptDesigner { constructor() { this.currentTemplate = null; this.selectedElement = null; this.zoomLevel = 100; this.history = []; this.historyIndex = -1; this.isWYSIWYGMode = false; this.inlineToolbar = null; this.debounceTimer = null; this.companySaveTimer = null; this.initializeApp(); this.bindEvents(); this.setupTemplates(); this.initializeDefaultMode(); // Add security protections this.addCSPProtection(); this.validateReceiptData(); } initializeApp() { // Initialize DOM elements this.receiptPage = document.getElementById('receiptPage'); this.formatToolbar = document.getElementById('formatToolbar'); this.logoModal = document.getElementById('logoModal'); // Setup default receipt data with localStorage support this.receiptData = { company: this.loadCompanyData() || { name: 'บริษัท ตัวอย่าง จำกัด', address: '123 ถนนสุขุมวิท แขวงคลองตัน เขตวัฒนา กรุงเทพฯ 10110', phone: '02-123-4567', email: 'info@company.com', taxId: '0123456789012' }, customer: { name: 'คุณลูกค้า ตัวอย่าง', address: '456 ถนนรัชดา เขตห้วยขวาง กรุงเทพฯ 10310', phone: '08-1234-5678' }, receipt: { number: 'R2024001', date: new Date().toLocaleDateString('th-TH'), dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toLocaleDateString('th-TH') }, items: [ {description: 'สินค้า/บริการ 1', quantity: 2, price: 500, total: 1000}, {description: 'สินค้า/บริการ 2', quantity: 1, price: 750, total: 750}, {description: 'สินค้า/บริการ 3', quantity: 3, price: 300, total: 900} ], totals: { subtotal: 2650, vat: 185.5, total: 2835.5 } }; } bindEvents() { // Template selection document.querySelectorAll('.template-card').forEach(card => { card.addEventListener('click', (e) => { const template = e.currentTarget.dataset.template; this.selectTemplate(template); }); }); // Toolbar buttons document.getElementById('newReceipt').addEventListener('click', () => this.newReceipt()); document.getElementById('printReceipt').addEventListener('click', () => this.printReceipt()); document.getElementById('exportPdf').addEventListener('click', () => this.exportToPDF()); // Company data buttons document.getElementById('resetCompanyData').addEventListener('click', () => this.resetCompanyData()); document.getElementById('undo').addEventListener('click', () => this.undo()); document.getElementById('redo').addEventListener('click', () => this.redo()); document.getElementById('zoomIn').addEventListener('click', () => this.zoomIn()); document.getElementById('zoomOut').addEventListener('click', () => this.zoomOut()); // Mode buttons document.getElementById('editMode').addEventListener('click', () => this.enableWYSIWYGMode()); document.getElementById('previewMode').addEventListener('click', () => this.disableWYSIWYGMode()); // Format toolbar events this.bindFormatToolbarEvents(); // Text selection for WYSIWYG document.addEventListener('mouseup', () => this.handleTextSelection()); document.addEventListener('keyup', () => this.handleTextSelection()); // Tool buttons document.getElementById('addLogo').addEventListener('click', () => this.showLogoModal()); document.getElementById('addField').addEventListener('click', () => this.addField()); // Color and font changes document.getElementById('primaryColor').addEventListener('change', (e) => { this.updatePrimaryColor(e.target.value); }); document.getElementById('secondaryColor').addEventListener('change', (e) => { this.updateSecondaryColor(e.target.value); }); document.getElementById('fontFamily').addEventListener('change', (e) => { this.updateFontFamily(e.target.value); }); // VAT Rate change document.getElementById('vatRate').addEventListener('change', () => { this.calculateTotals(); this.updateVatLabel(); }); // Modal events document.querySelector('.modal-close').addEventListener('click', () => this.hideModal()); document.getElementById('logoUpload').addEventListener('click', () => { document.getElementById('logoInput').click(); }); document.getElementById('logoInput').addEventListener('change', (e) => { this.handleLogoUpload(e); }); // Drag and drop for logo const logoUpload = document.getElementById('logoUpload'); logoUpload.addEventListener('dragover', (e) => { e.preventDefault(); logoUpload.style.backgroundColor = '#f0f8ff'; }); logoUpload.addEventListener('dragleave', () => { logoUpload.style.backgroundColor = ''; }); logoUpload.addEventListener('drop', (e) => { e.preventDefault(); logoUpload.style.backgroundColor = ''; const files = e.dataTransfer.files; if (files.length > 0) { this.processLogoFile(files[0]); } }); // Click outside to deselect document.addEventListener('click', (e) => { if (!e.target.closest('.editable') && !e.target.closest('.format-toolbar')) { this.deselectElement(); this.hideInlineToolbar(); } }); } // WYSIWYG Mode Functions enableWYSIWYGMode() { this.isWYSIWYGMode = true; document.body.classList.add('wysiwyg-mode'); // Update mode buttons document.getElementById('editMode').classList.add('active'); document.getElementById('previewMode').classList.remove('active'); // Don't show format toolbar immediately - it will appear on text selection // this.formatToolbar.style.display = 'block'; // Make all editable elements content editable this.makeElementsContentEditable(true); // Show edit mode indicator this.showEditModeIndicator(); } disableWYSIWYGMode() { this.isWYSIWYGMode = false; document.body.classList.remove('wysiwyg-mode'); // Update mode buttons document.getElementById('editMode').classList.remove('active'); document.getElementById('previewMode').classList.add('active'); // Hide format toolbar this.formatToolbar.style.display = 'none'; // Disable content editing this.makeElementsContentEditable(false); // Hide indicator this.hideEditModeIndicator(); } makeElementsContentEditable(enable) { const editableElements = this.receiptPage.querySelectorAll('.editable'); editableElements.forEach(element => { // ไม่ให้แก้ไขช่องยอดรวม ภาษี และรวมทั้งสิ้น const isCalculatedField = element.classList.contains('item-total') || element.closest('.total-box') || element.closest('.total-section') || element.closest('[class*="total"]'); if (enable && !isCalculatedField) { element.setAttribute('contenteditable', 'true'); element.addEventListener('input', this.handleContentEdit.bind(this)); element.addEventListener('focus', this.handleElementFocus.bind(this)); element.addEventListener('blur', this.handleElementBlur.bind(this)); } else { element.removeAttribute('contenteditable'); element.removeEventListener('input', this.handleContentEdit.bind(this)); element.removeEventListener('focus', this.handleElementFocus.bind(this)); element.removeEventListener('blur', this.handleElementBlur.bind(this)); } }); } handleContentEdit(e) { const element = e.target; // Sync content with receiptData this.syncElementToReceiptData(element); // ตรวจสอบว่าเป็นการแก้ไขข้อมูลในตารางสินค้าหรือไม่ if (element.classList.contains('item-quantity') || element.classList.contains('item-price') || element.closest('.items-table')) { // คำนวณยอดรวมใหม่หลังจากการแก้ไข setTimeout(() => this.calculateTotals(), 300); } // Debounced save this.debouncedSave(); } handleElementFocus(e) { this.selectedElement = e.target; e.target.classList.add('selected'); } handleElementBlur(e) { e.target.classList.remove('selected'); this.debouncedSave(); } handleTextSelection() { if (!this.isWYSIWYGMode) return; const selection = window.getSelection(); if (selection.rangeCount > 0 && !selection.isCollapsed) { const range = selection.getRangeAt(0); const selectedElement = range.commonAncestorContainer.nodeType === Node.TEXT_NODE ? range.commonAncestorContainer.parentElement : range.commonAncestorContainer; if (selectedElement.closest('.editable')) { this.showInlineToolbar(range); this.updateToolbarState(); } } else { this.hideInlineToolbar(); } } showInlineToolbar(range) { const rect = range.getBoundingClientRect(); const toolbar = this.formatToolbar; // Show the toolbar toolbar.style.display = 'block'; // Position it above the selection const toolbarRect = toolbar.getBoundingClientRect(); let left = rect.left + rect.width / 2 - toolbarRect.width / 2; let top = rect.top - toolbarRect.height - 10; // Keep toolbar within viewport const viewport = { width: window.innerWidth, height: window.innerHeight }; if (left < 10) left = 10; if (left + toolbarRect.width > viewport.width - 10) { left = viewport.width - toolbarRect.width - 10; } if (top < 10) { // Show below selection if no room above top = rect.bottom + 10; } toolbar.style.left = left + 'px'; toolbar.style.top = top + 'px'; } hideInlineToolbar() { if (this.formatToolbar) { this.formatToolbar.style.display = 'none'; } } updateToolbarState() { // Update toolbar button states based on current selection const selection = window.getSelection(); if (selection.rangeCount === 0) return; // Check if formatting is applied const isBold = document.queryCommandState('bold'); const isItalic = document.queryCommandState('italic'); const isUnderline = document.queryCommandState('underline'); // Update button states this.toggleToolbarButton('bold', isBold); this.toggleToolbarButton('italic', isItalic); this.toggleToolbarButton('underline', isUnderline); } toggleToolbarButton(command, isActive) { const button = document.querySelector(`[data-command="${command}"]`); if (button) { if (isActive) { button.classList.add('active'); } else { button.classList.remove('active'); } } } bindFormatToolbarEvents() { // Format buttons const toolbarButtons = document.querySelectorAll('.format-toolbar .toolbar-btn[data-command]'); toolbarButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const command = button.dataset.command; this.executeFormatCommand(command); }); }); // Font size select const fontSizeSelect = document.getElementById('fontSizeSelect'); if (fontSizeSelect) { fontSizeSelect.addEventListener('change', (e) => { this.applyFontSize(e.target.value); }); } // Text color const textColor = document.getElementById('textColor'); if (textColor) { textColor.addEventListener('change', (e) => { this.applyTextColor(e.target.value); }); } // Add item button const addItemBtn = document.getElementById('addItemBtn'); if (addItemBtn) { addItemBtn.addEventListener('click', () => { this.addNewItem(); }); } } executeFormatCommand(command = '') { document.execCommand(command, false, null); this.updateToolbarState(); this.debouncedSave(); } applyFontSize(size) { const selection = window.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); if (!range.collapsed) { document.execCommand('fontSize', false, '7'); const selectedElement = range.commonAncestorContainer.parentElement; const fontElements = selectedElement.querySelectorAll('font[size="7"]'); fontElements.forEach(el => { el.style.fontSize = size + 'px'; el.removeAttribute('size'); }); this.debouncedSave(); } } } applyTextColor(color) { document.execCommand('foreColor', false, color); this.debouncedSave(); } syncElementToReceiptData(element) { const classList = Array.from(element.classList); const content = this.validateString(element.textContent.trim()); let isCompanyData = false; if (classList.includes('company-name')) { this.receiptData.company.name = content; isCompanyData = true; } else if (classList.includes('company-address')) { this.receiptData.company.address = content; isCompanyData = true; } else if (classList.includes('company-phone')) { // ลบข้อความ "โทร: " ออกหากมี const phoneContent = content.replace(/^โทร:\s*/, '').trim(); this.receiptData.company.phone = phoneContent; isCompanyData = true; } else if (classList.includes('company-email')) { // ลบข้อความ "อีเมล: " ออกหากมี const emailContent = content.replace(/^อีเมล:\s*/, '').trim(); this.receiptData.company.email = emailContent; isCompanyData = true; } else if (classList.includes('company-tax-id')) { // ลบข้อความ "เลขประจำตัวผู้เสียภาษี: " ออกหากมี const taxIdContent = content.replace(/^เลขประจำตัวผู้เสียภาษี:\s*/, '').trim(); this.receiptData.company.taxId = taxIdContent; isCompanyData = true; } else if (classList.includes('customer-name')) { this.receiptData.customer.name = content; } else if (classList.includes('customer-address')) { this.receiptData.customer.address = content; } else if (classList.includes('customer-phone')) { this.receiptData.customer.phone = content; } else if (classList.includes('receipt-number')) { // ลบเครื่องหมาย # หากมี const receiptNumber = content.replace(/^#/, '').trim(); this.receiptData.receipt.number = receiptNumber; } else if (classList.includes('receipt-date')) { this.receiptData.receipt.date = content; } else if (classList.includes('receipt-due-date')) { this.receiptData.receipt.dueDate = content; } // บันทึกข้อมูลบริษัทอัตโนมัติเมื่อมีการแก้ไข if (isCompanyData) { this.debouncedSaveCompany(); } } debouncedSave() { clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(() => { this.saveToHistory(); }, 500); } debouncedSaveCompany() { clearTimeout(this.companySaveTimer); this.companySaveTimer = setTimeout(() => { this.saveCompanyData(); }, 1000); } showEditModeIndicator() { let indicator = document.querySelector('.edit-mode-indicator'); if (!indicator) { indicator = document.createElement('div'); indicator.className = 'edit-mode-indicator'; indicator.innerHTML = ' โหมดแก้ไข'; document.body.appendChild(indicator); } indicator.style.display = 'flex'; } hideEditModeIndicator() { const indicator = document.querySelector('.edit-mode-indicator'); if (indicator) { indicator.style.display = 'none'; } } setupTemplates() { this.templates = { modern: { name: 'โมเดิร์น', html: this.getModernTemplate() }, classic: { name: 'คลาสสิก', html: this.getClassicTemplate() }, minimal: { name: 'มินิมอล', html: this.getMinimalTemplate() } }; } selectTemplate(templateName) { // Update UI document.querySelectorAll('.template-card').forEach(card => { card.classList.remove('active'); }); document.querySelector(`[data-template="${templateName}"]`).classList.add('active'); this.currentTemplate = templateName; this.receiptPage.innerHTML = this.templates[templateName].html; // Populate data into template this.populateTemplateData(); // Make elements editable this.makeElementsEditable(); // Apply WYSIWYG mode if enabled if (this.isWYSIWYGMode) { this.makeElementsContentEditable(true); } // อัปเดตข้อความภาษีให้ตรงกับอัตราที่เลือก this.updateVatLabel(); // Enable buttons document.getElementById('printReceipt').disabled = false; document.getElementById('exportPdf').disabled = false; // Save to history this.saveToHistory(); } populateTemplateData() { // Populate company data const companyName = this.receiptPage.querySelector('.company-name'); if (companyName) companyName.textContent = this.receiptData.company.name; const companyAddress = this.receiptPage.querySelector('.company-address'); if (companyAddress) companyAddress.textContent = this.receiptData.company.address; const companyPhone = this.receiptPage.querySelector('.company-phone'); if (companyPhone) companyPhone.textContent = this.receiptData.company.phone; const companyEmail = this.receiptPage.querySelector('.company-email'); if (companyEmail) companyEmail.textContent = this.receiptData.company.email; const companyTaxId = this.receiptPage.querySelector('.company-tax-id'); if (companyTaxId) companyTaxId.textContent = this.receiptData.company.taxId; // Populate customer data const customerName = this.receiptPage.querySelector('.customer-name'); if (customerName) customerName.textContent = this.receiptData.customer.name; const customerAddress = this.receiptPage.querySelector('.customer-address'); if (customerAddress) customerAddress.textContent = this.receiptData.customer.address; const customerPhone = this.receiptPage.querySelector('.customer-phone'); if (customerPhone) customerPhone.textContent = this.receiptData.customer.phone; // Populate receipt data const receiptNumber = this.receiptPage.querySelector('.receipt-number'); if (receiptNumber) receiptNumber.textContent = this.receiptData.receipt.number; const receiptDate = this.receiptPage.querySelector('.receipt-date'); if (receiptDate) receiptDate.textContent = this.receiptData.receipt.date; const receiptDueDate = this.receiptPage.querySelector('.receipt-due-date'); if (receiptDueDate) receiptDueDate.textContent = this.receiptData.receipt.dueDate; // Populate items table this.populateItemsTable(); } populateItemsTable() { const itemsTableBody = this.receiptPage.querySelector('.items-table tbody'); if (!itemsTableBody) return; // Clear existing rows except template const rows = itemsTableBody.querySelectorAll('tr:not(.item-template)'); rows.forEach(row => row.remove()); // Add items this.receiptData.items.forEach((item, index) => { this.addItemRow(item, index); }); // Update totals this.updateTotals(); } makeElementsEditable() { const editableElements = this.receiptPage.querySelectorAll('.editable'); editableElements.forEach(element => { // Basic click handler for selection (always active) element.addEventListener('click', (e) => { e.stopPropagation(); this.selectElement(element); }); // Input handler for traditional editing element.addEventListener('input', () => { if (!this.isWYSIWYGMode) { this.saveToHistory(); } }); element.addEventListener('blur', () => { if (!this.isWYSIWYGMode) { this.saveToHistory(); } }); }); // Setup add/remove buttons for items table this.setupItemTableControls(); } setupItemTableControls() { // Setup existing remove buttons this.setupRemoveButtons(); } setupRemoveButtons() { const removeButtons = this.receiptPage.querySelectorAll('.remove-item-btn'); removeButtons.forEach((button) => { button.addEventListener('click', (e) => { const row = e.target.closest('tr'); this.deleteRow(row); }); }); } addNewItem() { const newItem = { description: 'สินค้า/บริการใหม่', quantity: 1, price: 0, total: 0 }; this.receiptData.items.push(newItem); this.addItemRow(newItem, this.receiptData.items.length - 1); this.updateTotals(); this.saveToHistory(); } addItemRow(item, index) { const itemsTableBody = this.receiptPage.querySelector('.items-table tbody'); if (!itemsTableBody) return; const row = document.createElement('tr'); row.className = 'item-row'; row.innerHTML = `
Debug: Canvas รูปแบบไม่ถูกต้อง
`; document.body.appendChild(debugDiv); const debugCanvas = debugDiv.querySelector('#debugCanvas'); debugCanvas.width = Math.min(280, canvas.width); debugCanvas.height = Math.min(200, canvas.height); const debugCtx = debugCanvas.getContext('2d'); debugCtx.drawImage(canvas, 0, 0, debugCanvas.width, debugCanvas.height); // Try with minimal settings const fallbackCanvas = await html2canvas(tempContainer, { scale: 1, backgroundColor: '#ffffff', logging: true, useCORS: false, allowTaint: false, onrendered: (canvas) => { console.log('Fallback canvas rendered'); } }); if (fallbackCanvas && fallbackCanvas.width > 0 && fallbackCanvas.height > 0) { console.log('Using fallback canvas'); canvas = fallbackCanvas; } else { throw new Error('ไม่สามารถสร้าง Canvas ได้ กรุณาลองใหม่อีกครั้ง'); } } // Clean up temporary container document.body.removeChild(tempContainer); // Restore original elements actionButtons.forEach(el => { el.style.display = ''; }); // Create PDF from canvas await this.createPDFFromCanvas(canvas); } catch (error) { console.error('Export PDF Error:', error); this.showNotification('❌ เกิดข้อผิดพลาดในการส่งออก PDF: ' + error.message, 'error'); } finally { // Restore button const exportBtn = document.getElementById('exportPdf'); exportBtn.innerHTML = ' Export PDF'; exportBtn.disabled = false; } } async createPDFFromCanvas(canvas) { // Create PDF with better settings const {jsPDF} = window.jspdf; const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4', compress: true, precision: 2 }); // Calculate optimal dimensions with better fitting const pdfWidth = 210; // A4 width in mm const pdfHeight = 297; // A4 height in mm const margin = 10; // Small margin for better appearance const availableWidth = pdfWidth - (margin * 2); const availableHeight = pdfHeight - (margin * 2); // Calculate scaling to fit content properly const scaleX = availableWidth / (canvas.width / 3.78); // Convert pixels to mm const scaleY = availableHeight / (canvas.height / 3.78); const scale = Math.min(scaleX, scaleY, 1); // Don't scale up const imgWidth = (canvas.width / 3.78) * scale; const imgHeight = (canvas.height / 3.78) * scale; // Center the content const offsetX = margin + (availableWidth - imgWidth) / 2; const offsetY = margin; // Convert canvas to optimized image with white background const tempCanvas = document.createElement('canvas'); tempCanvas.width = canvas.width; tempCanvas.height = canvas.height; const tempCtx = tempCanvas.getContext('2d'); // Fill with white background first tempCtx.fillStyle = '#ffffff'; tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); // Then draw the original canvas on top tempCtx.drawImage(canvas, 0, 0); const imgData = tempCanvas.toDataURL('image/jpeg', 0.95); // Smart page handling if (imgHeight <= availableHeight) { // Single page - center content pdf.addImage(imgData, 'JPEG', offsetX, offsetY, imgWidth, imgHeight); } else { // Multiple pages with proper breaks let currentY = offsetY; let remainingHeight = imgHeight; let sourceY = 0; while (remainingHeight > 0) { const currentPageHeight = Math.min(remainingHeight, availableHeight); const sourceHeight = (currentPageHeight / imgHeight) * canvas.height; // Create canvas section for current page const pageCanvas = document.createElement('canvas'); const pageCtx = pageCanvas.getContext('2d'); pageCanvas.width = canvas.width; pageCanvas.height = sourceHeight; // Fill with white background pageCtx.fillStyle = '#ffffff'; pageCtx.fillRect(0, 0, pageCanvas.width, pageCanvas.height); // Draw section of original canvas pageCtx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight); const pageImgData = pageCanvas.toDataURL('image/jpeg', 0.95); // Add to PDF pdf.addImage(pageImgData, 'JPEG', offsetX, currentY, imgWidth, currentPageHeight); // Prepare for next page remainingHeight -= currentPageHeight; sourceY += sourceHeight; if (remainingHeight > 0) { pdf.addPage(); currentY = offsetY; } } } // Add metadata to PDF pdf.setProperties({ title: `ใบเสร็จรับเงิน ${this.receiptData.receipt.number}`, subject: 'Receipt', author: this.receiptData.company.name, creator: 'Receipt Designer', producer: 'Receipt Designer' }); // Generate clean filename const receiptNumber = this.receiptData.receipt.number?.replace(/[^a-zA-Z0-9]/g, '') || 'R001'; const now = new Date(); const dateStr = now.toISOString().slice(0, 10).replace(/-/g, ''); const timeStr = now.toTimeString().slice(0, 5).replace(':', ''); const filename = `Receipt-${receiptNumber}-${dateStr}-${timeStr}.pdf`; // Save PDF pdf.save(filename); // Success message with file info this.showNotification(`✅ ส่งออก PDF สำเร็จ! ไฟล์: ${filename}`, 'success'); } showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 1rem 1.5rem; border-radius: 6px; color: white; font-weight: 500; z-index: 10000; max-width: 300px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transform: translateX(100%); transition: transform 0.3s ease; `; // Set background color based on type switch (type) { case 'success': notification.style.background = '#27ae60'; break; case 'error': notification.style.background = '#e74c3c'; break; default: notification.style.background = '#3498db'; } document.body.appendChild(notification); // Animate in setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); // Auto remove after 3 seconds setTimeout(() => { notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (notification.parentNode) { document.body.removeChild(notification); } }, 300); }, 3000); } // รายการสินค้า Methods addRow() { if (!this.currentTemplate) return; const table = this.receiptPage.querySelector('.items-table tbody'); if (!table) return; const newIndex = table.children.length; const newRow = document.createElement('tr'); newRow.setAttribute('data-row-index', newIndex); // สร้างแถวใหม่ตามรูปแบบของเทมเพลต if (this.currentTemplate === 'modern') { newRow.innerHTML = `${this.receiptData.company.address}
โทร: ${this.receiptData.company.phone}
อีเมล: ${this.receiptData.company.email}
เลขประจำตัวผู้เสียภาษี: ${this.receiptData.company.taxId}
${this.receiptData.customer.name}
${this.receiptData.customer.address}
โทร: ${this.receiptData.customer.phone}
| รายการ | จำนวน | ราคาต่อหน่วย | รวม | จัดการ |
|---|---|---|---|---|
| ${item.description} | ${item.quantity} | ${item.price.toLocaleString()} | ${item.total.toLocaleString()} |
${this.receiptData.company.address}
โทร: ${this.receiptData.company.phone} | อีเมล: ${this.receiptData.company.email}
เลขประจำตัวผู้เสียภาษี: ${this.receiptData.company.taxId}
เลขที่: ${this.receiptData.receipt.number}
วันที่: ${this.receiptData.receipt.date}
ครบกำหนด: ${this.receiptData.receipt.dueDate}
ชื่อ: ${this.receiptData.customer.name}
ที่อยู่: ${this.receiptData.customer.address}
โทรศัพท์: ${this.receiptData.customer.phone}
| รายการสินค้า/บริการ | จำนวน | ราคาต่อหน่วย (บาท) | จำนวนเงิน (บาท) | จัดการ |
|---|---|---|---|---|
| ${item.description} | ${item.quantity} | ${item.price.toLocaleString()} | ${item.total.toLocaleString()} |
ขอบพระคุณที่ใช้บริการ
กรุณาเก็บใบเสร็จนี้ไว้เป็นหลักฐานการชำระเงิน
${this.receiptData.company.email}
#${this.receiptData.receipt.number}
จาก
${this.receiptData.company.name}
${this.receiptData.company.address}
ถึง
${this.receiptData.customer.name}
${this.receiptData.customer.address}
| รายการ | จำนวน | ราคา | รวม | จัดการ |
|---|---|---|---|---|
| ${item.description} | ${item.quantity} | ${item.price.toLocaleString()} | ${item.total.toLocaleString()} |
วันที่: ${this.receiptData.receipt.date}
ขอบคุณสำหรับการใช้บริการ