const escapeHtml = (value) => { const stringValue = value == null ? '' : String(value); return stringValue.replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character] || character)); }; const humanizeInvoiceText = (value) => { const raw = String(value ?? '').trim(); if (!raw) { return '---'; } return raw .replace(/[_-]+/g, ' ') .replace(/\s+/g, ' ') .toLowerCase() .replace(/\b\w/g, (letter) => letter.toUpperCase()); }; const formatInvoiceCurrency = (amount, decimals = 3) => { const numericAmount = Number.isFinite(parseFloat(amount)) ? parseFloat(amount) : 0; return `${numericAmount.toFixed(decimals)}`; }; const setInvoiceText = (id, value) => { const element = document.getElementById(id); if (element) { element.textContent = value == null || value === '' ? '---' : String(value); } return element; }; const toggleInvoiceField = (containerId, valueId, value) => { const container = document.getElementById(containerId); const valueElement = document.getElementById(valueId); if (!container || !valueElement) { return; } if (value) { valueElement.textContent = value; container.style.display = 'block'; } else { valueElement.textContent = ''; container.style.display = 'none'; } }; let invoicePrintOriginalParent = null; let invoicePrintOriginalNextSibling = null; let invoicePrintRestoreTimer = null; const prepareInvoiceForPrint = () => { const modal = document.getElementById('viewInvoiceModal'); if (!modal || !document.body) { return; } if (!invoicePrintOriginalParent) { invoicePrintOriginalParent = modal.parentNode; invoicePrintOriginalNextSibling = modal.nextSibling; } if (modal.parentNode !== document.body) { document.body.appendChild(modal); } document.body.classList.add('printing-invoice'); }; const restoreInvoiceAfterPrint = () => { const modal = document.getElementById('viewInvoiceModal'); if (!document.body) { return; } document.body.classList.remove('printing-invoice'); if (!modal || !invoicePrintOriginalParent || modal.parentNode === invoicePrintOriginalParent) { return; } invoicePrintOriginalParent.insertBefore(modal, invoicePrintOriginalNextSibling); }; window.printInvoiceDocument = function() { prepareInvoiceForPrint(); window.print(); if (invoicePrintRestoreTimer) { window.clearTimeout(invoicePrintRestoreTimer); } invoicePrintRestoreTimer = window.setTimeout(() => { restoreInvoiceAfterPrint(); }, 1200); }; window.addEventListener('beforeprint', prepareInvoiceForPrint); window.addEventListener('afterprint', restoreInvoiceAfterPrint); window.viewAndPrintA4Invoice = function(data, autoPrint = true) { if (!data) return; const invoiceType = String(data.type || 'sale').toLowerCase() === 'purchase' ? 'purchase' : 'sale'; const invoiceDisplayNo = data.document_no || data.transaction_no || ((invoiceType === 'purchase' ? 'PUR-' : 'INV-') + data.id.toString().padStart(5, '0')); const paymentText = humanizeInvoiceText(data.payment_type || 'cash'); const statusKey = String(data.status || '').toLowerCase(); const statusText = statusKey === 'partially_paid' ? 'Partially Paid' : humanizeInvoiceText(statusKey); const typeText = invoiceType === 'purchase' ? 'Purchase Invoice' : 'Sales Invoice'; const documentTitle = invoiceType === 'purchase' ? 'Purchase Invoice / فاتورة شراء' : 'Tax Invoice / فاتورة ضريبية'; const documentSubtitle = invoiceType === 'purchase' ? 'Official purchase record / مستند شراء رسمي' : 'Official tax document / مستند ضريبي رسمي'; const partyLabelText = invoiceType === 'purchase' ? 'Supplier Details / بيانات المورد' : 'Bill To / بيانات العميل'; const partyLabelEn = invoiceType === 'purchase' ? 'Supplier Details' : 'Bill To'; const partyLabelAr = invoiceType === 'purchase' ? 'بيانات المورد' : 'بيانات العميل'; const formatQty = typeof window.formatQuantity === 'function' ? window.formatQuantity : (value) => { const numericValue = Number.isFinite(parseFloat(value)) ? parseFloat(value) : 0; return Number.isInteger(numericValue) ? String(numericValue) : numericValue.toFixed(3).replace(/\.?0+$/, ''); }; setInvoiceText('invNumber', invoiceDisplayNo); setInvoiceText('invDate', data.invoice_date || '---'); setInvoiceText('invPaymentType', paymentText); setInvoiceText('invPaymentTypeSummary', paymentText); setInvoiceText('invCustomerName', data.customer_name || '---'); setInvoiceText('invStatusText', statusText); setInvoiceText('invDocumentTitle', documentTitle); setInvoiceText('invDocumentSubtitle', documentSubtitle); setInvoiceText('invAmountInWords', data.total_in_words || '---'); toggleInvoiceField('invCustomerPhoneContainer', 'invCustomerPhone', data.customer_phone || ''); toggleInvoiceField('invCustomerTaxIdContainer', 'invCustomerTaxId', data.customer_tax_id || ''); toggleInvoiceField('invOutletRow', 'invOutletName', data.outlet_name || ''); const partyLabel = document.getElementById('invPartyLabel'); if (partyLabel) { partyLabel.textContent = partyLabelText; partyLabel.setAttribute('data-en', partyLabelEn); partyLabel.setAttribute('data-ar', partyLabelAr); } const invoiceTypeLabel = document.getElementById('invoiceTypeLabel'); if (invoiceTypeLabel) { invoiceTypeLabel.textContent = typeText; invoiceTypeLabel.className = 'invoice-pill ' + (invoiceType === 'purchase' ? 'invoice-pill--purchase' : 'invoice-pill--sale'); } const statusLabel = document.getElementById('invoiceStatusLabel'); let statusClass = 'invoice-pill--neutral'; if (statusKey === 'paid') { statusClass = 'invoice-pill--paid'; } else if (statusKey === 'unpaid') { statusClass = 'invoice-pill--unpaid'; } else if (statusKey === 'partially_paid') { statusClass = 'invoice-pill--partial'; } if (statusLabel) { statusLabel.textContent = statusText; statusLabel.className = 'invoice-pill ' + statusClass; } const body = document.getElementById('invItemsBody'); if (body) { body.innerHTML = ''; if (Array.isArray(data.items) && data.items.length > 0) { data.items.forEach((item, index) => { const tr = document.createElement('tr'); const englishName = escapeHtml(item.name_en || item.name || 'Item'); const arabicName = escapeHtml(item.name_ar || ''); const itemNameHtml = arabicName ? `
${englishName}
${arabicName}
` : `
${englishName}
`; tr.innerHTML = ` ${index + 1} ${itemNameHtml} ${formatQty(item.quantity)} ${formatInvoiceCurrency(item.unit_price, 3)} ${(Number.isFinite(parseFloat(item.vat_rate)) ? parseFloat(item.vat_rate) : 0).toFixed(2)}% ${formatInvoiceCurrency(item.total_price, 3)} `; body.appendChild(tr); }); } else { body.innerHTML = 'No invoice items / لا توجد أصناف'; } } const vatVal = Number.isFinite(parseFloat(data.vat_amount)) ? parseFloat(data.vat_amount) : 0; const totalVal = Number.isFinite(parseFloat(data.total_amount)) ? parseFloat(data.total_amount) : 0; const discountVal = Math.max(0, Number.isFinite(parseFloat(data.discount_amount)) ? parseFloat(data.discount_amount) : 0); const isPosInvoice = String(data.is_pos || '0') === '1'; const grossBeforeDiscount = isPosInvoice ? totalVal : (totalVal + vatVal); const grandTotalParsed = parseFloat(data.total_with_vat); const grandTotalValue = Number.isFinite(grandTotalParsed) ? grandTotalParsed : Math.max(0, grossBeforeDiscount - discountVal); const subtotalExVat = isPosInvoice ? Math.max(0, grossBeforeDiscount - vatVal) : totalVal; const paidAmount = Number.isFinite(parseFloat(data.paid_amount)) ? parseFloat(data.paid_amount) : 0; const balance = Math.max(0, grandTotalValue - paidAmount); const subtotalEl = document.getElementById('invSubtotal'); if (subtotalEl) subtotalEl.innerHTML = formatInvoiceCurrency(subtotalExVat, 3); const vatEl = document.getElementById('invVatAmount'); if (vatEl) vatEl.innerHTML = formatInvoiceCurrency(vatVal, 3); const discountRow = document.getElementById('invDiscountRow'); const discountAmountEl = document.getElementById('invDiscountAmount'); if (discountRow && discountAmountEl) { if (discountVal > 0) { discountRow.style.display = 'flex'; discountAmountEl.innerHTML = formatInvoiceCurrency(discountVal, 3); } else { discountRow.style.display = 'none'; discountAmountEl.innerHTML = ''; } } const grandTotalEl = document.getElementById('invGrandTotal'); if (grandTotalEl) grandTotalEl.innerHTML = formatInvoiceCurrency(grandTotalValue, 3); const paidInfoEl = document.getElementById('invPaidInfo'); if (paidInfoEl) paidInfoEl.innerHTML = formatInvoiceCurrency(paidAmount, 3); const balanceInfoEl = document.getElementById('invBalanceInfo'); if (balanceInfoEl) balanceInfoEl.innerHTML = formatInvoiceCurrency(balance, 3); const companyName = ; const vatNo = ; const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: ${invoiceDisplayNo}\nDate: ${data.invoice_date}\nTotal: ${grandTotalValue.toFixed(3)}`; const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=112x112&data=${encodeURIComponent(qrData)}`; const qrCode = document.getElementById('invQrCode'); if (qrCode) { qrCode.innerHTML = `QR Code`; } const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal')); viewModal.show(); const paymentsPromise = fetch(`index.php?action=get_payments&invoice_id=${data.id}`) .then((res) => res.json()) .then((payments) => { const paymentsBody = document.getElementById('invPaymentsBody'); const paymentsSection = document.getElementById('invPaymentsSection'); if (paymentsBody) paymentsBody.innerHTML = ''; if (Array.isArray(payments) && payments.length > 0) { if (paymentsBody) { payments.forEach((payment) => { const paymentChip = document.createElement('div'); paymentChip.className = 'invoice-payment-pill'; paymentChip.innerHTML = ` ${escapeHtml(payment.payment_date || '')} ${escapeHtml(humanizeInvoiceText(payment.payment_method || ''))} ${formatInvoiceCurrency(payment.amount, 3)} `; paymentsBody.appendChild(paymentChip); }); } if (paymentsSection) paymentsSection.style.display = 'block'; } else if (paymentsSection) { paymentsSection.style.display = 'none'; } }) .catch((err) => { console.error('Error fetching payments:', err); const paymentsSection = document.getElementById('invPaymentsSection'); if (paymentsSection) paymentsSection.style.display = 'none'; }); if (autoPrint) { paymentsPromise.finally(() => { setTimeout(() => { window.printInvoiceDocument(); }, 450); }); } }; prepare("SELECT doc.*, c.name AS customer_name, c.phone AS customer_phone, $autoTaxSelect, $autoOutletSelect FROM $autoTable doc LEFT JOIN $autoPartyTable c ON doc.$autoPartyCol = c.id $autoOutletJoin WHERE doc.id = ? LIMIT 1"); $autoStmt->execute([$autoInvoiceId]); $autoInvoicePayload = $autoStmt->fetch(PDO::FETCH_ASSOC); if ($autoInvoicePayload) { $autoItemSql = $autoInvoiceType === 'purchase' ? "SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?" : "SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?"; $autoItemsStmt = db()->prepare($autoItemSql); $autoItemsStmt->execute([$autoInvoiceId]); $autoInvoicePayload['items'] = $autoItemsStmt->fetchAll(PDO::FETCH_ASSOC); $autoInvoicePayload['type'] = $autoInvoiceType; $autoInvoicePayload['total_with_vat'] = (float)($autoInvoicePayload['total_with_vat'] ?? (($autoInvoicePayload['total_amount'] ?? 0) + ($autoInvoicePayload['vat_amount'] ?? 0))); $autoInvoicePayload['paid_amount'] = (float)($autoInvoicePayload['paid_amount'] ?? 0); $autoInvoicePayload['total_in_words'] = numberToWordsOMR($autoInvoicePayload['total_with_vat']); $autoTransactionNo = trim((string)($autoInvoicePayload['transaction_no'] ?? '')); $autoPrefix = $autoInvoiceType === 'purchase' ? 'PUR' : 'INV'; $autoInvoicePayload['document_no'] = ($autoInvoiceType === 'sale' && $autoTransactionNo !== '') ? $autoTransactionNo : $autoPrefix . '-' . str_pad((string)$autoInvoicePayload['id'], 5, '0', STR_PAD_LEFT); } } catch (Throwable $e) { $autoInvoicePayload = null; } } ?> setTimeout(() => { if (window.viewAndPrintA4Invoice) { window.viewAndPrintA4Invoice(, true); } }, 300);