diff --git a/index.php b/index.php index 1cc5bd2..b14e2b2 100644 --- a/index.php +++ b/index.php @@ -2106,20 +2106,73 @@ if (isset($_GET['action']) || isset($_POST['action'])) { exit; } + if ($action === 'get_invoice_details') { + header('Content-Type: application/json'); + $invoice_id = (int)($_GET['invoice_id'] ?? 0); + $type = (($_GET['type'] ?? 'sale') === 'purchase') ? 'purchase' : 'sale'; + + if ($invoice_id < 1) { + echo json_encode(['success' => false, 'error' => 'Invalid invoice id']); + exit; + } + + $table = ($type === 'purchase') ? 'purchases' : 'invoices'; + $itemTable = ($type === 'purchase') ? 'purchase_items' : 'invoice_items'; + $fkColumn = ($type === 'purchase') ? 'purchase_id' : 'invoice_id'; + $partyColumn = ($type === 'purchase') ? 'supplier_id' : 'customer_id'; + $partyTable = ($type === 'purchase') ? 'suppliers' : 'customers'; + $partyAlias = ($type === 'purchase') ? 'supplier_name' : 'customer_name'; + + $where = ['v.id = ?']; + $params = [$invoice_id]; + if (db_column_exists($table, 'outlet_id')) { + $oid = current_outlet_id(); + if ($oid !== -1) { + $where[] = '(v.outlet_id = ? OR v.outlet_id IS NULL)'; + $params[] = $oid; + } + } + + $stmt = db()->prepare("SELECT v.*, c.name AS {$partyAlias}, c.phone AS party_phone FROM $table v LEFT JOIN $partyTable c ON v.$partyColumn = c.id WHERE " . implode(' AND ', $where) . " LIMIT 1"); + $stmt->execute($params); + $invoice = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$invoice) { + echo json_encode(['success' => false, 'error' => 'Invoice not found']); + exit; + } + + $partyName = trim((string)($invoice[$partyAlias] ?? '')); + if ($partyName === '' && $type === 'sale' && !empty($invoice['is_pos'])) { + $partyName = 'Walk-in Customer'; + } + + $invoice['type'] = $type; + $invoice['party_name'] = $partyName !== '' ? $partyName : '---'; + $invoice['paid_amount'] = (float)($invoice['paid_amount'] ?? 0); + + $stmtItems = db()->prepare("SELECT li.*, i.name_en, i.name_ar, i.sku, i.vat_rate, i.stock_quantity FROM $itemTable li LEFT JOIN stock_items i ON li.item_id = i.id WHERE li.$fkColumn = ?"); + $stmtItems->execute([$invoice_id]); + $invoice['items'] = $stmtItems->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode($invoice, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); + exit; + } + if ($action === 'get_invoice_items') { header('Content-Type: application/json'); $invoice_id = (int)$_GET['invoice_id']; $type = $_GET['type'] ?? 'sale'; if ($type === 'purchase') { - $stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.sku + $stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.sku, i.vat_rate, i.stock_quantity FROM purchase_items pi - JOIN stock_items i ON pi.item_id = i.id + LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?"); } else { - $stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.sku + $stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.sku, i.vat_rate, i.stock_quantity FROM invoice_items ii - JOIN stock_items i ON ii.item_id = i.id + LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?"); } $stmt->execute([$invoice_id]); diff --git a/pages/sales_purchases_invoice_actions_script.php b/pages/sales_purchases_invoice_actions_script.php index a1e4d65..fb6e408 100644 --- a/pages/sales_purchases_invoice_actions_script.php +++ b/pages/sales_purchases_invoice_actions_script.php @@ -1,223 +1,148 @@ // Edit Invoice Logic - document.querySelectorAll('.edit-invoice-btn').forEach(btn => { - btn.addEventListener('click', function() { - const data = JSON.parse(this.dataset.json); - document.getElementById('edit_invoice_id').value = data.id; - document.getElementById('edit_customer_id').value = data.customer_id; - document.getElementById('edit_invoice_date').value = data.invoice_date; - document.getElementById('edit_due_date').value = data.due_date || ''; - document.getElementById('edit_payment_type').value = data.payment_type || 'cash'; - document.getElementById('edit_status').value = data.status || 'unpaid'; - document.getElementById('edit_paid_amount').value = parseFloat(data.paid_amount || 0).toFixed(3); - - if (data.status === 'partially_paid') { - document.getElementById('editPaidAmountContainer').style.display = 'block'; - } else { - document.getElementById('editPaidAmountContainer').style.display = 'none'; - } - - const tableBody = document.getElementById('editInvoiceItemsTableBody'); - tableBody.innerHTML = ''; - - data.items.forEach(item => { - // We need more data than what's in invoice_items (like SKU and names, but we have them from the join in PHP) - // The dataset-json already contains name_en, name_ar etc because of the PHP logic at line 1093 - const itemMeta = { - id: item.item_id, - name_en: item.name_en, - name_ar: item.name_ar, - sku: '', // Optional, or fetch if needed - vat_rate: 0 // Will be handled if we have it in the join - }; + const parseInvoiceButtonPayload = (btn) => { + if (!btn || !btn.dataset || !btn.dataset.json) return {}; + try { + return JSON.parse(btn.dataset.json); + } catch (error) { + console.warn('Failed to parse invoice payload from button data.', error); + return {}; + } + }; - // Fetch current item details to get VAT rate if possible, or use stored if available - // For simplicity, let's assume we want to use the item's current VAT rate or store it. - // Looking at the join at line 1093, it doesn't fetch vat_rate. Let's fix that in PHP too. - - addItemToTable({ - id: item.item_id, - name_en: item.name_en, - name_ar: item.name_ar, - sku: '', - vat_rate: item.vat_rate || 0 // We'll add this to PHP join - }, tableBody, null, null, - document.getElementById('edit_grandTotal'), - document.getElementById('edit_subtotal'), - document.getElementById('edit_totalVat'), - { quantity: item.quantity, unit_price: item.unit_price }); + const syncSelect2Value = (select) => { + if (!select) return; + if (select.classList.contains('select2') && window.jQuery && jQuery.fn && jQuery.fn.select2) { + jQuery(select).trigger('change'); + } + }; + + const ensureSelectOption = (selectId, value, label = '') => { + const select = document.getElementById(selectId); + if (!select || value === null || value === undefined || String(value) === '') return; + + const alreadyExists = Array.from(select.options || []).some(option => option.value == String(value)); + if (!alreadyExists) { + const option = document.createElement('option'); + option.value = String(value); + option.textContent = label || String(value); + select.appendChild(option); + } + }; + + const setBlankSelectOptionLabel = (select, label = '---') => { + if (!select) return; + const emptyOption = Array.from(select.options || []).find(option => option.value === ''); + if (emptyOption) { + emptyOption.textContent = label; + } + }; + + const normalizeEditPaymentType = (paymentType) => { + let normalized = String(paymentType || 'cash').toLowerCase().replace(/[\s-]+/g, '_'); + if (normalized === 'pos') normalized = 'cash'; + if (!['cash', 'card', 'bank_transfer', 'credit'].includes(normalized)) { + normalized = 'cash'; + } + return normalized; + }; + + const renderEditInvoiceItems = (items) => { + const tableBody = document.getElementById('editInvoiceItemsTableBody'); + const grandTotalEl = document.getElementById('edit_grandTotal'); + const subtotalEl = document.getElementById('edit_subtotal'); + const totalVatEl = document.getElementById('edit_totalVat'); + + if (!tableBody) return; + tableBody.innerHTML = ''; + + if (!Array.isArray(items) || items.length === 0) { + if (typeof recalculate === 'function') { + recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl); + } + return; + } + + items.forEach(item => { + addItemToTable({ + id: item.item_id || item.id, + name_en: item.name_en || item.item_name_en || 'Item', + name_ar: item.name_ar || item.item_name_ar || '', + sku: item.sku || '', + vat_rate: item.vat_rate || 0, + stock_quantity: item.stock_quantity || 0 + }, tableBody, null, null, grandTotalEl, subtotalEl, totalVatEl, { + quantity: item.quantity, + unit_price: item.unit_price }); }); + }; + + const populateEditInvoiceModal = (data) => { + if (!data) return; + + const invoiceIdInput = document.getElementById('edit_invoice_id'); + const partySelect = document.getElementById('edit_customer_id'); + const invoiceDateInput = document.getElementById('edit_invoice_date'); + const dueDateInput = document.getElementById('edit_due_date'); + const paymentTypeSelect = document.getElementById('edit_payment_type'); + const statusSelect = document.getElementById('edit_status'); + const paidAmountInput = document.getElementById('edit_paid_amount'); + const paidAmountContainer = document.getElementById('editPaidAmountContainer'); + + const partyId = data.customer_id ?? data.supplier_id ?? ''; + const partyLabel = data.party_name || data.customer_name || data.supplier_name || ''; + + ensureSelectOption('edit_customer_id', partyId, partyLabel); + + if (invoiceIdInput) invoiceIdInput.value = data.id || ''; + if (partySelect) { + setBlankSelectOptionLabel(partySelect, (partyId === '' && partyLabel) ? partyLabel : '---'); + partySelect.value = partyId; + syncSelect2Value(partySelect); + } + if (invoiceDateInput) invoiceDateInput.value = data.invoice_date || ''; + if (dueDateInput) dueDateInput.value = data.due_date || ''; + if (paymentTypeSelect) paymentTypeSelect.value = normalizeEditPaymentType(data.payment_type); + if (statusSelect) statusSelect.value = data.status || 'unpaid'; + if (paidAmountInput) paidAmountInput.value = parseFloat(data.paid_amount || 0).toFixed(3); + if (paidAmountContainer) { + paidAmountContainer.style.display = data.status === 'partially_paid' ? 'block' : 'none'; + } + + renderEditInvoiceItems(data.items || []); + }; + + document.querySelectorAll('.edit-invoice-btn').forEach(btn => { + btn.addEventListener('click', async function() { + const fallbackData = parseInvoiceButtonPayload(this); + const invoiceId = this.dataset.id || fallbackData.id || ''; + const type = this.dataset.type || fallbackData.type || invoiceType || 'sale'; + const tableBody = document.getElementById('editInvoiceItemsTableBody'); + + if (Object.keys(fallbackData).length > 0) { + populateEditInvoiceModal(fallbackData); + } else if (tableBody) { + tableBody.innerHTML = '
Loading invoice...'; + } + + if (!invoiceId) return; + + try { + const resp = await fetch(`index.php?action=get_invoice_details&invoice_id=${encodeURIComponent(invoiceId)}&type=${encodeURIComponent(type)}`); + const data = await resp.json(); + + if (data && data.id) { + populateEditInvoiceModal(data); + } else if (data && data.error) { + throw new Error(data.error); + } + } catch (error) { + console.error('Failed to load invoice details for edit modal.', error); + if (window.Swal) { + Swal.fire('Error', 'Failed to load invoice details', 'error'); + } + } + }); }); // View and Print Invoice Logic - document.addEventListener('click', function(e) { - if (e.target.closest('.view-invoice-btn')) { - const btn = e.target.closest('.view-invoice-btn'); - const data = JSON.parse(btn.dataset.json); - if (window.viewAndPrintA4Invoice) { - window.viewAndPrintA4Invoice(data, false); - } - } - if (e.target.closest('.print-a4-btn')) { - const btn = e.target.closest('.print-a4-btn'); - const data = JSON.parse(btn.dataset.json); - if (window.viewAndPrintA4Invoice) { - window.viewAndPrintA4Invoice(data, true); - } - } - }); - - // Return Logic (General for Sales and Purchase) - const setupReturnLogic = (selectId, containerId, tbodyId, totalDisplayId, submitBtnId, type = "sale") => { - const select = document.getElementById(selectId); - if (!select) return; - - const calculateTotal = function() { - let total = 0; - document.querySelectorAll('#' + tbodyId + ' tr').forEach(row => { - const qtyInput = row.querySelector('.return-qty-input'); - if (!qtyInput) return; - const qty = parseFloat(qtyInput.value) || 0; - const price = parseFloat(qtyInput.dataset.price) || 0; - const lineTotal = qty * price; - const lineTotalDisplay = row.querySelector('.line-total'); - if (lineTotalDisplay) { - lineTotalDisplay.innerText = lineTotal.toFixed(3); - } - total += lineTotal; - }); - const totalDisplay = document.getElementById(totalDisplayId); - if (totalDisplay) { - totalDisplay.innerText = 'OMR ' + total.toFixed(3); - } - const submitBtn = document.getElementById(submitBtnId); - if (submitBtn) { - submitBtn.disabled = total <= 0; - } - }; - - const handleInvoiceChange = async function() { - const invoiceId = select.value; - const container = document.getElementById(containerId); - const tbody = document.getElementById(tbodyId); - const submitBtn = document.getElementById(submitBtnId); - - if (!invoiceId) { - if (container) container.style.display = 'none'; - if (submitBtn) submitBtn.disabled = true; - return; - } - - if (tbody) { - tbody.innerHTML = '
Loading items...'; - } - if (container) container.style.display = 'block'; - - try { - const resp = await fetch(`index.php?action=get_invoice_items&invoice_id=${invoiceId}&type=${type}`); - const items = await resp.json(); - - if (tbody) { - tbody.innerHTML = ''; - if (items.length === 0) { - tbody.innerHTML = 'No items found for this invoice.'; - } else { - let html = ''; - items.forEach(item => { - html += ` - - ${item.name_en}
${item.sku} - ${parseFloat(item.quantity).toFixed(2)} - - - - - - ${parseFloat(item.unit_price).toFixed(3)} - 0.000 - - `; - }); - tbody.innerHTML = html; - } - } - - if (submitBtn) submitBtn.disabled = true; - - const qtyInputs = tbody.querySelectorAll('.return-qty-input'); - qtyInputs.forEach(input => { - ['input', 'change', 'keyup'].forEach(evt => input.addEventListener(evt, calculateTotal)); - }); - - calculateTotal(); - } catch (e) { - console.error(e); - if (window.Swal) Swal.fire('Error', 'Failed to fetch invoice items', 'error'); - } - }; - - select.addEventListener('change', handleInvoiceChange); - if (window.jQuery && jQuery.fn.select2) { - $(select).on('select2:select change', handleInvoiceChange); - } - }; - - setupReturnLogic('return_invoice_select', 'return_items_container', 'return_items_tbody', 'return_total_display', 'submit_return_btn', 'sale'); - setupReturnLogic('purchase_return_invoice_select', 'purchase_return_items_container', 'purchase_return_items_tbody', 'purchase_return_total_display', 'purchase_submit_return_btn', 'purchase'); - - // Return Invoice Button from Sales/Purchases list - document.querySelectorAll('.return-invoice-btn').forEach(btn => { - btn.addEventListener('click', function() { - const invoiceId = this.dataset.id; - const targetModal = this.dataset.bsTarget; - const selectId = targetModal === '#addSalesReturnModal' ? 'return_invoice_select' : 'purchase_return_invoice_select'; - const select = document.getElementById(selectId); - if (select) { - $(select).val(invoiceId).trigger('change'); - } - }); - }); - - // View Return Logic - document.querySelectorAll('.view-return-btn').forEach(btn => { - btn.addEventListener('click', async function() { - const returnId = this.dataset.id; - const type = ''; - const modal = new bootstrap.Modal(document.getElementById('viewReturnDetailsModal')); - - try { - const resp = await fetch(`index.php?action=get_return_details&return_id=${returnId}&type=${type}`); - const data = await resp.json(); - - if (data) { - document.getElementById('view_return_no').innerText = (type === 'purchase' ? 'PRET-' : 'SRET-') + String(data.id).padStart(5, '0'); - document.getElementById('view_return_party').innerText = data.party_name; - document.getElementById('view_return_date').innerText = data.return_date; - const refId = data.purchase_id || data.invoice_id; - const refPrefix = type === 'purchase' ? 'PUR-' : 'INV-'; - document.getElementById('view_return_invoice').innerText = refPrefix + String(refId).padStart(5, '0'); - document.getElementById('view_return_total').innerText = 'OMR ' + parseFloat(data.total_amount).toFixed(3); - document.getElementById('view_return_notes').innerText = data.notes || 'No notes'; - - let itemsHtml = ''; - data.items.forEach(item => { - itemsHtml += ` - - ${item.name_en}
${item.sku} - ${parseFloat(item.quantity).toFixed(2)} - ${parseFloat(item.unit_price).toFixed(3)} - ${parseFloat(item.total_price).toFixed(3)} - - `; - }); - document.getElementById('view_return_items_tbody').innerHTML = itemsHtml; - modal.show(); - } - } catch (e) { - console.error(e); - Swal.fire('Error', 'Failed to fetch return details', 'error'); - } - }); - }); diff --git a/pages/sales_purchases_logic.php b/pages/sales_purchases_logic.php index a7fdabe..d138091 100644 --- a/pages/sales_purchases_logic.php +++ b/pages/sales_purchases_logic.php @@ -130,11 +130,11 @@ $inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']); if ($type === 'sale') { - $item_stmt = db()->prepare("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 = ?"); + $item_stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.sku, i.vat_rate, i.stock_quantity FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?"); $item_stmt->execute([$inv['id']]); $inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC); } else { - $item_stmt = db()->prepare("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 = ?"); + $item_stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.sku, i.vat_rate, i.stock_quantity FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?"); $item_stmt->execute([$inv['id']]); $inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC); } diff --git a/pages/sales_purchases_modals.php b/pages/sales_purchases_modals.php index d9443ab..2a47364 100644 --- a/pages/sales_purchases_modals.php +++ b/pages/sales_purchases_modals.php @@ -519,7 +519,7 @@
- > diff --git a/pages/sales_purchases_save_logic.php b/pages/sales_purchases_save_logic.php index 090a5f8..ccd4a4f 100644 --- a/pages/sales_purchases_save_logic.php +++ b/pages/sales_purchases_save_logic.php @@ -12,7 +12,8 @@ $cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id'; $fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id'; - $cust_id = (int)$_POST['customer_id']; + $rawCustomerId = $_POST['customer_id'] ?? ''; + $cust_id = ($type === 'sale' && ($rawCustomerId === '' || $rawCustomerId === null)) ? null : (int)$rawCustomerId; $inv_date = $_POST['invoice_date'] ?: date('Y-m-d'); $due_date = $_POST['due_date'] ?: null; $status = $_POST['status'] ?? 'pending'; @@ -111,7 +112,8 @@ $cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id'; $fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id'; - $cust_id = (int)$_POST['customer_id']; + $rawCustomerId = $_POST['customer_id'] ?? ''; + $cust_id = ($type === 'sale' && ($rawCustomerId === '' || $rawCustomerId === null)) ? null : (int)$rawCustomerId; $date = $_POST['invoice_date'] ?: date('Y-m-d'); $due_date = $_POST['due_date'] ?: null; $status = $_POST['status'] ?? 'pending'; diff --git a/pages/sales_purchases_view.php b/pages/sales_purchases_view.php index cb9f163..fa96413 100644 --- a/pages/sales_purchases_view.php +++ b/pages/sales_purchases_view.php @@ -164,14 +164,15 @@ OMR OMR +
- + - + - +
diff --git a/tmp_inline_script.js b/tmp_inline_script.js new file mode 100644 index 0000000..0b3214d --- /dev/null +++ b/tmp_inline_script.js @@ -0,0 +1,1066 @@ + +document.addEventListener('DOMContentLoaded', function() { + console.log("DOM Content Loaded - Accounting System"); + try { + // Initialize Select2 for all searchable dropdowns + $('.select2').each(function() { + $(this).select2({ + width: '100%', + dropdownParent: $(this).closest('.modal').length ? $(this).closest('.modal') : $(document.body) + }); + }); + + const hasExpiryToggle = document.getElementById('hasExpiryToggle'); + const expiryDateContainer = document.getElementById('expiryDateContainer'); + const suggestSkuBtn = document.getElementById('suggestSkuBtn'); + const skuInput = document.getElementById('skuInput'); + + if (suggestSkuBtn && skuInput) { + suggestSkuBtn.addEventListener('click', function() { + const sku = Math.floor(100000000000 + Math.random() * 900000000000).toString(); + skuInput.value = sku; + }); + + skuInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + console.log("Barcode scan detected in SKU field, preventing form submission"); + } + }); + } + + // Toggle Expiry Date visibility + if (hasExpiryToggle && expiryDateContainer) { + hasExpiryToggle.addEventListener('change', function() { + expiryDateContainer.style.display = this.checked ? 'block' : 'none'; + }); + } + + const isPromotionToggle = document.getElementById('isPromotionToggle'); + const promotionFieldsContainer = document.getElementById('promotionFieldsContainer'); + if (isPromotionToggle && promotionFieldsContainer) { + isPromotionToggle.addEventListener('change', function() { + promotionFieldsContainer.style.display = this.checked ? 'flex' : 'none'; + }); + } + + // Translation Logic + document.querySelectorAll('.btn-translate').forEach(btn => { + btn.addEventListener('click', function() { + const sourceId = this.getAttribute('data-source'); + const targetId = this.getAttribute('data-target'); + const targetLang = this.getAttribute('data-to'); + const sourceText = document.getElementById(sourceId).value; + + if (!sourceText) { + alert(targetLang === 'ar' ? 'يرجى إدخال النص أولاً' : 'Please enter text first'); + return; + } + + const originalHtml = this.innerHTML; + this.disabled = true; + this.innerHTML = ''; + + const formData = new FormData(); + formData.append('action', 'translate'); + formData.append('text', sourceText); + formData.append('target', targetLang); + + fetch('index.php', { + method: 'POST', + body: formData + }) + .then(res => res.json()) + .then(data => { + if (data.success) { + document.getElementById(targetId).value = data.translated; + } else { + alert('Translation error: ' + (data.error || 'Unknown error')); + } + }) + .catch(err => { + console.error(err); + alert('Connection error'); + }) + .finally(() => { + this.disabled = false; + this.innerHTML = originalHtml; + }); + }); + }); + + document.querySelectorAll('.isPromotionToggleEdit').forEach(toggle => { + toggle.addEventListener('change', function() { + const id = this.getAttribute('data-id'); + const container = document.getElementById('promotionFieldsContainerEdit' + id); + if (container) { + container.style.display = this.checked ? 'flex' : 'none'; + } + }); + }); + + // Handle Expiry toggle in Edit Modals + document.querySelectorAll('.hasExpiryToggleEdit').forEach(toggle => { + toggle.addEventListener('change', function() { + const container = this.closest('.row').querySelector('.expiryDateContainerEdit'); + if (container) { + container.style.display = this.checked ? 'block' : 'none'; + if (!this.checked) { + container.querySelector('input').value = ''; + } + } + }); + }); + + // LPO Form Logic + initInvoiceForm('lpoProductSearchInput', 'lpoSearchSuggestions', 'lpoItemsTableBody', 'lpo_grand_display', 'lpo_subtotal_display', 'lpo_vat_display'); + initInvoiceForm('editLpoProductSearchInput', 'editLpoSearchSuggestions', 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display'); + + document.querySelectorAll('.edit-lpo-btn').forEach(btn => { + btn.addEventListener('click', function() { + const data = JSON.parse(this.dataset.json); + document.getElementById('edit_lpo_id').value = data.id; + const supplierSelect = document.getElementById('edit_lpo_supplier_id'); + supplierSelect.value = data.supplier_id; + if (window.jQuery && $(supplierSelect).data('select2')) { + $(supplierSelect).trigger('change'); + } + document.getElementById('edit_lpo_date').value = data.lpo_date; + document.getElementById('edit_lpo_delivery_date').value = data.delivery_date || ''; + document.getElementById('edit_lpo_status').value = data.status || 'pending'; + document.getElementById('edit_lpo_terms').value = data.terms_conditions || ''; + + const tableBody = document.getElementById('editLpoItemsTableBody'); + tableBody.innerHTML = ''; + + data.items.forEach(item => { + addItemToTable({ + id: item.item_id, + name_en: item.name_en, + name_ar: item.name_ar, + sku: '', + vat_rate: item.vat_rate || 0 + }, tableBody, null, null, + document.getElementById('edit_lpo_grand_display'), + document.getElementById('edit_lpo_subtotal_display'), + document.getElementById('edit_lpo_vat_display'), + { quantity: item.quantity, unit_price: item.unit_price }); + }); + }); + }); + + document.querySelectorAll('.view-lpo-btn').forEach(btn => { + btn.addEventListener('click', function() { + const data = JSON.parse(this.dataset.json); + window.viewAndPrintLPO(data); + }); + }); + + window.viewAndPrintLPO = function(data) { + const modal = new bootstrap.Modal(document.getElementById('viewLpoModal')); + const content = document.getElementById('lpoDetailsContent'); + + const logoUrl = companySettings.company_logo || ''; + const companyHeader = ` +
+
+ ${logoUrl ? `Logo` : ''} +

${companySettings.company_name || 'Your Company'}

+

+ ${companySettings.company_address || ''}
+ Phone: ${companySettings.company_phone || ''} | Email: ${companySettings.company_email || ''} + ${companySettings.tax_number ? `
TRN: ${companySettings.tax_number}` : ''} +

+
+
+

LOCAL PURCHASE ORDER

+

LPO-${data.id.toString().padStart(5, '0')}

+
+
+ `; + + let itemsHtml = ''; + data.items.forEach((item, index) => { + itemsHtml += ` + + ${index + 1} + ${item.name_en}
${item.name_ar} + ${item.quantity} + ${parseFloat(item.unit_price).toFixed(3)} + ${parseFloat(item.vat_rate || 0).toFixed(2)}% + ${parseFloat(item.total_amount).toFixed(3)} + + `; + }); + + content.innerHTML = ` + ${companyHeader} +
+
+
+
Supplier
+

${data.supplier_name}

+

+ ${data.supplier_phone ? `Phone: ${data.supplier_phone}` : ''} +

+
+
+
Details
+
+ Date: + ${data.lpo_date} +
+
+ Delivery: + ${data.delivery_date || '---'} +
+
+ Status: + ${data.status.toUpperCase()} +
+
+
+
+ + + + + + + + + + + + ${itemsHtml} + + + + + + + + + + + + + + +
#DescriptionQtyUnit PriceVATTotal
SubtotalOMR ${parseFloat(data.total_amount).toFixed(3)}
VAT AmountOMR ${parseFloat(data.vat_amount).toFixed(2)}
Grand TotalOMR ${parseFloat(data.total_with_vat).toFixed(3)}
+
+ ${data.terms_conditions ? ` +
+
+
Terms & Conditions
+

${data.terms_conditions.replace(/\n/g, '
')}

+
+
` : ''} + +
+
+
+

Prepared By

+
+
+
+
+
+

Authorized Signature

+
+
+
+ `; + + window.printLPO = function() { + const printWindow = window.open('', '_blank'); + printWindow.document.write('LPO-' + data.id + ''); + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write(content.innerHTML); + printWindow.document.write(''); + printWindow.document.close(); + setTimeout(() => { + printWindow.print(); + printWindow.close(); + }, 500); + }; + + modal.show(); + }; + + // Quotation Form Logic + initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display'); + initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display'); + + document.querySelectorAll('.edit-quotation-btn').forEach(btn => { + btn.addEventListener('click', function() { + const data = JSON.parse(this.dataset.json); + document.getElementById('edit_quotation_id').value = data.id; + document.getElementById('edit_quot_customer_id').value = data.customer_id; + document.getElementById('edit_quot_date').value = data.quotation_date; + document.getElementById('edit_quot_valid').value = data.valid_until || ''; + document.getElementById('edit_quot_status').value = data.status || 'pending'; + + const tableBody = document.getElementById('editQuotItemsTableBody'); + tableBody.innerHTML = ''; + + data.items.forEach(item => { + addItemToTable({ + id: item.item_id, + name_en: item.name_en, + name_ar: item.name_ar, + sku: '', + vat_rate: item.vat_rate || 0 + }, tableBody, null, null, + document.getElementById('edit_quot_grand_display'), + document.getElementById('edit_quot_subtotal_display'), + document.getElementById('edit_quot_vat_display'), + { quantity: item.quantity, unit_price: item.unit_price }); + }); + }); + }); + + document.querySelectorAll('.convert-quotation-btn').forEach(btn => { + btn.addEventListener('click', function() { + if (confirm('Convert this quotation to an invoice? This will reduce stock.')) { + const f = document.createElement('form'); + f.method = 'POST'; + f.innerHTML = ``; + document.body.appendChild(f); + f.submit(); + } + }); + }); + + // View Quotation Logic + window.viewAndPrintQuotation = function(data, autoPrint = false) { + const modal = new bootstrap.Modal(document.getElementById('viewQuotationModal')); + const content = document.getElementById('quotationPrintableArea'); + + let itemsHtml = ''; + data.items.forEach((item, index) => { + itemsHtml += ` + + ${index + 1} + ${item.name_en}
${item.name_ar} + ${item.quantity} + ${parseFloat(item.unit_price).toFixed(3)} + ${parseFloat(item.vat_rate || 0).toFixed(2)}% + ${parseFloat(item.total_price).toFixed(3)} + + `; + }); + + // Company Logo and Header Construction + const logoUrl = companySettings.company_logo || ''; + const logoImg = logoUrl ? `` : ''; + const companyName = companySettings.company_name || 'Accounting System'; + const companyAddress = (companySettings.company_address || '').replace(/\n/g, '
'); + const companyVat = companySettings.vat_number ? `

VAT: ${companySettings.vat_number}

` : ''; + const companyPhone = companySettings.company_phone ? `

Tel: ${companySettings.company_phone}

` : ''; + + // Quotation Header Construction + const quotDate = data.quotation_date; + const quotValid = data.valid_until || 'N/A'; + const quotNo = 'QUO-' + data.id.toString().padStart(5, '0'); + const customerName = data.customer_name || 'Walk-in Customer'; + const statusBadge = `${data.status.toUpperCase()}`; + + content.innerHTML = ` +
+
+
+
+ ${logoImg} +

${companyName}

+

${companyAddress}

+ ${companyVat} + ${companyPhone} +
+
+

Quotation / عرض سعر

+
${statusBadge}
+
+

No / رقم: ${quotNo}

+

Date / التاريخ: ${quotDate}

+

Valid Until / صالح لغاية: ${quotValid}

+
+
+
+
+ +
+
+
+

To / إلى

+
${customerName}
+
+
+
+ + + + + + + + + + + + + ${itemsHtml} + + + + + + + + + + + + + + +
#Item Description / وصف الصنفQty / الكميةUnit Price / سعر الوحدةVAT / الضريبةTotal / الإجمالي
Subtotal / المجموع الفرعي${parseFloat(data.total_amount).toFixed(3)}
VAT Amount / مبلغ الضريبة${parseFloat(data.vat_amount).toFixed(2)}
Grand Total (OMR) / المجموع الكلي (رع)${parseFloat(data.total_with_vat).toFixed(3)}
+ +
+
+
+

Terms & Conditions / الشروط والأحكام:

+
    +
  • Quotation is valid until the date mentioned above. / عرض السعر صالح لغاية التاريخ المذكور أعلاه.
  • +
  • Prices are inclusive of VAT where applicable. / الأسعار تشمل ضريبة القيمة المضافة حيثما ينطبق ذلك.
  • +
+
+
+
Authorized Signature / التوقيع المعتمد
+
+
+
+
+

Generated by / تم إنشاؤه بواسطة ${companyName}

+
+
+ `; + + const actionButtons = document.getElementById('quotationActionButtons'); + actionButtons.innerHTML = ''; + if (data.status === 'pending') { + const convertBtn = document.createElement('button'); + convertBtn.className = 'btn btn-success me-2'; + convertBtn.innerHTML = ' Convert to Invoice'; + convertBtn.onclick = function() { + if (confirm('Convert this quotation to an invoice?')) { + const f = document.createElement('form'); + f.method = 'POST'; + f.innerHTML = ``; + document.body.appendChild(f); + f.submit(); + } + }; + actionButtons.appendChild(convertBtn); + + const editBtn = document.createElement('button'); + editBtn.className = 'btn btn-primary'; + editBtn.innerHTML = ' Edit'; + editBtn.onclick = function() { + const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal')); + modal.hide(); + const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) || + document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`); + if (originalEditBtn) originalEditBtn.click(); + }; + actionButtons.appendChild(editBtn); + } + + modal.show(); + if (autoPrint) { + setTimeout(() => { window.print(); }, 500); + } + }; + + document.querySelectorAll('.view-quotation-btn').forEach(btn => { + btn.addEventListener('click', function() { + const data = JSON.parse(this.dataset.json); + window.viewAndPrintQuotation(data, false); + }); + }); + + + // Status change logic for Paid Amount field + const togglePaidAmount = (statusId, containerId) => { + const statusEl = document.getElementById(statusId); + const containerEl = document.getElementById(containerId); + if (statusEl && containerEl) { + statusEl.addEventListener('change', function() { + if (this.value === 'partially_paid') { + containerEl.style.display = 'block'; + } else { + containerEl.style.display = 'none'; + } + }); + } + }; + togglePaidAmount('add_status', 'addPaidAmountContainer'); + togglePaidAmount('edit_status', 'editPaidAmountContainer'); + + // Pay Invoice Logic + document.querySelectorAll('.pay-invoice-btn').forEach(btn => { + btn.addEventListener('click', function() { + const id = this.getAttribute('data-id'); + const total = parseFloat(this.getAttribute('data-total')); + const paid = parseFloat(this.getAttribute('data-paid') || 0); + const remaining = total - paid; + + document.getElementById('pay_invoice_id').value = id; + document.getElementById('pay_invoice_total').value = total.toFixed(3); + document.getElementById('pay_remaining_amount').value = remaining.toFixed(3); + document.getElementById('pay_amount').value = remaining.toFixed(3); + document.getElementById('pay_amount').max = remaining.toFixed(3); + }); + }); + + // Show receipt modal if needed + + function showReceipt(paymentId) { + fetch(`index.php?action=get_payment_details&payment_id=${paymentId}`) + .then(res => res.json()) + .then(data => { + if (!data) return; + document.getElementById('receiptNo').textContent = 'RCP-' + data.id.toString().padStart(5, '0'); + document.getElementById('receiptDate').textContent = data.payment_date; + document.getElementById('receiptCustomer').textContent = data.customer_name || '---'; + document.getElementById('receiptInvNo').textContent = (data.inv_type === 'purchase' ? 'PUR-' : 'INV-') + data.inv_id.toString().padStart(5, '0'); + document.getElementById('receiptMethod').textContent = data.payment_method; + document.getElementById('receiptAmount').textContent = parseFloat(data.amount).toFixed(3); + document.getElementById('receiptAmountWords').textContent = data.amount_words; + + const outletEl = document.getElementById('receiptOutletName'); + if (outletEl) { + outletEl.textContent = data.outlet_name ? (data.outlet_name) : ''; + outletEl.style.display = data.outlet_name ? 'block' : 'none'; + } + + // Update labels for Purchase vs Sale + const partyLabel = document.getElementById('receiptPartyLabel'); + const againstLabel = document.getElementById('receiptAgainstLabel'); + const receiptTitle = document.querySelector('#receiptModal .modal-title'); + const receiptH4 = document.querySelector('.receipt-container h4.letter-spacing-2'); + + if (data.inv_type === 'purchase') { + partyLabel.textContent = 'Paid To'; + partyLabel.setAttribute('data-en', 'Paid To'); + partyLabel.setAttribute('data-ar', 'صرف إلى'); + + againstLabel.textContent = 'Against Purchase'; + againstLabel.setAttribute('data-en', 'Against Purchase'); + againstLabel.setAttribute('data-ar', 'مقابل شراء'); + + receiptTitle.textContent = 'Payment Voucher'; + receiptTitle.setAttribute('data-en', 'Payment Voucher'); + receiptTitle.setAttribute('data-ar', 'سند صرف'); + + receiptH4.textContent = 'PAYMENT VOUCHER'; + receiptH4.setAttribute('data-en', 'PAYMENT VOUCHER'); + receiptH4.setAttribute('data-ar', 'سند صرف'); + } else { + partyLabel.textContent = 'Received From'; + partyLabel.setAttribute('data-en', 'Received From'); + partyLabel.setAttribute('data-ar', 'وصلنا من'); + + againstLabel.textContent = 'Against Invoice'; + againstLabel.setAttribute('data-en', 'Against Invoice'); + againstLabel.setAttribute('data-ar', 'مقابل فاتورة'); + + receiptTitle.textContent = 'Payment Receipt'; + receiptTitle.setAttribute('data-en', 'Payment Receipt'); + receiptTitle.setAttribute('data-ar', 'سند قبض'); + + receiptH4.textContent = 'PAYMENT RECEIPT'; + receiptH4.setAttribute('data-en', 'PAYMENT RECEIPT'); + receiptH4.setAttribute('data-ar', 'سند قبض'); + } + + const notesContainer = document.getElementById('receiptNotesContainer'); + if (data.notes) { + document.getElementById('receiptNotes').textContent = data.notes; + notesContainer.style.display = 'block'; + } else { + notesContainer.style.display = 'none'; + } + + const receiptModal = new bootstrap.Modal(document.getElementById('receiptModal')); + receiptModal.show(); + }); + }; + + document.querySelectorAll('.view-payments-btn').forEach(btn => { + btn.addEventListener('click', function() { + const invoiceId = this.getAttribute('data-id'); + const tbody = document.getElementById('paymentsTableBody'); + tbody.innerHTML = 'Loading...'; + + fetch(`index.php?action=get_payments&invoice_id=${invoiceId}`) + .then(res => res.json()) + .then(data => { + tbody.innerHTML = ''; + if (data.length === 0) { + tbody.innerHTML = 'No payments found.'; + return; + } + data.forEach(p => { + const tr = document.createElement('tr'); + tr.innerHTML = ` + RCP-${p.id.toString().padStart(5, '0')} + ${p.payment_date} + ${p.payment_method} + OMR ${parseFloat(p.amount).toFixed(3)} + + + + `; + tbody.appendChild(tr); + }); + }); + }); + }); + + window.printReceipt = function() { + const content = document.getElementById('printableReceipt').innerHTML; + const originalContent = document.body.innerHTML; + document.body.innerHTML = content; + window.print(); + document.body.innerHTML = originalContent; + window.location.reload(); + }; + // Invoice Form Logic + const initInvoiceForm = (searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) => { + const searchInput = document.getElementById(searchInputId); + const suggestions = document.getElementById(suggestionsId); + const tableBody = document.getElementById(tableBodyId); + const grandTotalEl = document.getElementById(grandTotalId); + const subtotalEl = document.getElementById(subtotalId); + const totalVatEl = document.getElementById(totalVatId); + + if (!searchInput || !tableBody) return; + + let timeout = null; + searchInput.addEventListener('input', function() { + clearTimeout(timeout); + const q = this.value.trim(); + if (q.length < 2) { + suggestions.style.display = 'none'; + return; + } + + timeout = setTimeout(() => { + fetch(`index.php?action=search_items&q=${encodeURIComponent(q)}`) + .then(res => res.ok ? res.json() : []) + .then(data => { + suggestions.innerHTML = ''; + if (data.length > 0) { + data.forEach(item => { + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'list-group-item list-group-item-action'; + btn.innerHTML = ` +
+ ${item.sku} - ${item.name_en} / ${item.name_ar} + Stock: ${item.stock_quantity} +
+ `; + btn.onclick = () => addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl); + suggestions.appendChild(btn); + }); + suggestions.style.display = 'block'; + } else { + suggestions.style.display = 'none'; + } + }); + }, 300); + }); + + // Close suggestions when clicking outside + document.addEventListener('click', function(e) { + if (!searchInput.contains(e.target) && !suggestions.contains(e.target)) { + suggestions.style.display = 'none'; + } + }); + }; + + function addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) { + if (suggestions) suggestions.style.display = 'none'; + if (searchInput) searchInput.value = ''; + + const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1'); + const currentStock = parseFloat(item.stock_quantity) || 0; + + if (invoiceType === 'sale' && !allowZeroStock && !customData) { + const existingInTable = Array.from(tableBody.querySelectorAll('.item-row')).find(row => row.querySelector('.item-id-input').value == item.id); + let currentQtyInTable = 0; + if (existingInTable) { + currentQtyInTable = parseFloat(existingInTable.querySelector('.item-qty').value) || 0; + } + + if (currentQtyInTable + 1 > currentStock) { + alert('Insufficient stock! Available: ' + currentStock); + return; + } + } + + const existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id); + if (existingRow && !customData) { + const row = existingRow.closest('tr'); + const qtyInput = row.querySelector('.item-qty'); + qtyInput.value = parseFloat(qtyInput.value) + 1; + recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl); + return; + } + + const row = document.createElement('tr'); + row.className = 'item-row'; + const price = customData ? customData.unit_price : (invoiceType === 'sale' ? item.sale_price : item.purchase_price); + const qty = customData ? customData.quantity : 1; + const vatRate = item.vat_rate || 0; + + row.innerHTML = ` + + + + +
${item.name_en}
+
${item.name_ar} (${item.sku})
+ + + + + + + `; + + tableBody.appendChild(row); + attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl); + recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl); + } + + function recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl) { + let subtotal = 0; + let totalVat = 0; + tableBody.querySelectorAll('.item-row').forEach(row => { + const qty = parseFloat(row.querySelector('.item-qty').value) || 0; + const price = parseFloat(row.querySelector('.item-price').value) || 0; + const vatRate = parseFloat(row.querySelector('.item-vat-rate').value) || 0; + + const total = qty * price; + const vatAmount = total * (vatRate / 100); + + row.querySelector('.item-total').value = total.toFixed(3); + subtotal += total; + totalVat += vatAmount; + }); + const grandTotal = subtotal + totalVat; + + if (subtotalEl) subtotalEl.textContent = 'OMR ' + subtotal.toFixed(3); + if (totalVatEl) totalVatEl.textContent = 'OMR ' + totalVat.toFixed(2); + if (grandTotalEl) grandTotalEl.textContent = 'OMR ' + grandTotal.toFixed(3); + } + + function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) { + row.querySelector('.item-qty').addEventListener('input', function() { + const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1'); + if (invoiceType === 'sale' && !allowZeroStock) { + const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0; + const qty = parseFloat(this.value) || 0; + if (qty > stock) { + alert('Insufficient stock! Available: ' + stock); + this.value = stock; + } + } + recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl); + }); + row.querySelector('.item-price').addEventListener('input', () => recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl)); + row.querySelector('.remove-row').addEventListener('click', function() { + row.remove(); + recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl); + }); + } + + const invoiceType = 'sale'; + initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat'); + initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat'); + window.viewAndPrintA4Invoice = function(data, autoPrint = true) { + if (!data) return; + // Reuse view logic + const invoiceDisplayNo = data.document_no || data.transaction_no || ((data.type === 'purchase' ? 'PUR-' : 'INV-') + data.id.toString().padStart(5, '0')); + document.getElementById('invNumber').textContent = invoiceDisplayNo; + document.getElementById('invDate').textContent = data.invoice_date; + document.getElementById('invPaymentType').textContent = data.payment_type ? data.payment_type.toUpperCase() : 'CASH'; + document.getElementById('invCustomerName').textContent = data.customer_name || '---'; + + const phoneEl = document.getElementById('invCustomerPhone'); + const phoneContainer = document.getElementById('invCustomerPhoneContainer'); + if (data.customer_phone) { + phoneEl.textContent = data.customer_phone; + phoneContainer.style.display = 'block'; + } else { + phoneContainer.style.display = 'none'; + } + + const taxIdEl = document.getElementById('invCustomerTaxId'); + const taxIdContainer = document.getElementById('invCustomerTaxIdContainer'); + if (data.customer_tax_id) { + taxIdEl.textContent = data.customer_tax_id; + taxIdContainer.style.display = 'block'; + } else { + taxIdContainer.style.display = 'none'; + } + + document.getElementById('invAmountInWords').textContent = data.total_in_words || ''; + + const invOutletEl = document.getElementById('invOutletName'); + if (invOutletEl) { + invOutletEl.textContent = data.outlet_name ? (data.outlet_name) : ''; + invOutletEl.style.display = data.outlet_name ? 'block' : 'none'; + } + + document.getElementById('invPartyLabel').textContent = data.type === 'sale' ? 'Bill To / فاتورة إلى' : 'Bill From / فاتورة من'; + document.getElementById('invPartyLabel').setAttribute('data-en', data.type === 'sale' ? 'Bill To' : 'Bill From'); + document.getElementById('invPartyLabel').setAttribute('data-ar', data.type === 'sale' ? 'فاتورة إلى' : 'فاتورة من'); + document.getElementById('invoiceTypeLabel').textContent = data.type; + document.getElementById('invoiceTypeLabel').className = 'badge text-uppercase ' + (data.type === 'sale' ? 'bg-success' : 'bg-warning'); + + const statusLabel = document.getElementById('invoiceStatusLabel'); + let statusClass = 'bg-secondary'; + let statusEn = data.status ? (data.status.charAt(0).toUpperCase() + data.status.slice(1)) : '---'; + if (data.status === 'paid') statusClass = 'bg-success'; + else if (data.status === 'unpaid') statusClass = 'bg-danger'; + else if (data.status === 'partially_paid') { + statusClass = 'bg-warning text-dark'; + statusEn = 'Partially Paid'; + } + + statusLabel.textContent = statusEn; + statusLabel.className = 'badge text-uppercase ' + statusClass; + + const body = document.getElementById('invItemsBody'); + body.innerHTML = ''; + if (data.items) { + data.items.forEach(item => { + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${item.name_en} / ${item.name_ar} + ${item.quantity} + ر.ع / OMR ${parseFloat(item.unit_price).toFixed(3)} + ${parseFloat(item.vat_rate || 0).toFixed(2)}% + ر.ع / OMR ${parseFloat(item.total_price).toFixed(3)} + `; + body.appendChild(tr); + }); + } + const vatVal = parseFloat(data.vat_amount || 0); + const totalVal = parseFloat(data.total_amount || 0); + const grandTotalValue = (parseFloat(data.total_with_vat) || (totalVal + vatVal)); + + if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').innerHTML = 'ر.ع / OMR ' + (grandTotalValue - vatVal).toFixed(3); + if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').innerHTML = 'ر.ع / OMR ' + vatVal.toFixed(2); + if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').innerHTML = 'ر.ع / OMR ' + grandTotalValue.toFixed(3); + + if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').innerHTML = 'ر.ع / OMR ' + parseFloat(data.paid_amount || 0).toFixed(3); + const balance = grandTotalValue - parseFloat(data.paid_amount || 0); + if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').innerHTML = 'ر.ع / OMR ' + balance.toFixed(3); + + // Generate QR Code for Zakat, Tax and Customs Authority (ZATCA) style or simple formal + const companyName = "Bahjet Al-Safa Trading"; + const vatNo = "OM25418"; + 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=100x100&data=${encodeURIComponent(qrData)}`; + if (document.getElementById('invQrCode')) { + document.getElementById('invQrCode').innerHTML = `QR Code`; + } + + const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal')); + viewModal.show(); + + if (autoPrint) { + setTimeout(() => { window.print(); }, 1000); + } + + 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 (payments && payments.length > 0) { + if (paymentsBody) { + payments.forEach(p => { + const tr = document.createElement('tr'); + tr.innerHTML = `${p.payment_date}${p.payment_method}OMR ${parseFloat(p.amount).toFixed(3)}`; + paymentsBody.appendChild(tr); + }); + } + if (paymentsSection) paymentsSection.style.display = 'block'; + } else { + if (paymentsSection) paymentsSection.style.display = 'none'; + } + }).catch(err => console.error('Error fetching payments:', err)); + }; + + // Edit Invoice Logic + const parseInvoiceButtonPayload = (btn) => { + if (!btn || !btn.dataset || !btn.dataset.json) return {}; + try { + return JSON.parse(btn.dataset.json); + } catch (error) { + console.warn('Failed to parse invoice payload from button data.', error); + return {}; + } + }; + + const syncSelect2Value = (select) => { + if (!select) return; + if (select.classList.contains('select2') && window.jQuery && jQuery.fn && jQuery.fn.select2) { + jQuery(select).trigger('change'); + } + }; + + const ensureSelectOption = (selectId, value, label = '') => { + const select = document.getElementById(selectId); + if (!select || value === null || value === undefined || String(value) === '') return; + + const alreadyExists = Array.from(select.options || []).some(option => option.value == String(value)); + if (!alreadyExists) { + const option = document.createElement('option'); + option.value = String(value); + option.textContent = label || String(value); + select.appendChild(option); + } + }; + + const setBlankSelectOptionLabel = (select, label = '---') => { + if (!select) return; + const emptyOption = Array.from(select.options || []).find(option => option.value === ''); + if (emptyOption) { + emptyOption.textContent = label; + } + }; + + const normalizeEditPaymentType = (paymentType) => { + let normalized = String(paymentType || 'cash').toLowerCase().replace(/[\s-]+/g, '_'); + if (normalized === 'pos') normalized = 'cash'; + if (!['cash', 'card', 'bank_transfer', 'credit'].includes(normalized)) { + normalized = 'cash'; + } + return normalized; + }; + + const renderEditInvoiceItems = (items) => { + const tableBody = document.getElementById('editInvoiceItemsTableBody'); + const grandTotalEl = document.getElementById('edit_grandTotal'); + const subtotalEl = document.getElementById('edit_subtotal'); + const totalVatEl = document.getElementById('edit_totalVat'); + + if (!tableBody) return; + tableBody.innerHTML = ''; + + if (!Array.isArray(items) || items.length === 0) { + if (typeof recalculate === 'function') { + recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl); + } + return; + } + + items.forEach(item => { + addItemToTable({ + id: item.item_id || item.id, + name_en: item.name_en || item.item_name_en || 'Item', + name_ar: item.name_ar || item.item_name_ar || '', + sku: item.sku || '', + vat_rate: item.vat_rate || 0, + stock_quantity: item.stock_quantity || 0 + }, tableBody, null, null, grandTotalEl, subtotalEl, totalVatEl, { + quantity: item.quantity, + unit_price: item.unit_price + }); + }); + }; + + const populateEditInvoiceModal = (data) => { + if (!data) return; + + const invoiceIdInput = document.getElementById('edit_invoice_id'); + const partySelect = document.getElementById('edit_customer_id'); + const invoiceDateInput = document.getElementById('edit_invoice_date'); + const dueDateInput = document.getElementById('edit_due_date'); + const paymentTypeSelect = document.getElementById('edit_payment_type'); + const statusSelect = document.getElementById('edit_status'); + const paidAmountInput = document.getElementById('edit_paid_amount'); + const paidAmountContainer = document.getElementById('editPaidAmountContainer'); + + const partyId = data.customer_id ?? data.supplier_id ?? ''; + const partyLabel = data.party_name || data.customer_name || data.supplier_name || ''; + + ensureSelectOption('edit_customer_id', partyId, partyLabel); + + if (invoiceIdInput) invoiceIdInput.value = data.id || ''; + if (partySelect) { + setBlankSelectOptionLabel(partySelect, (partyId === '' && partyLabel) ? partyLabel : '---'); + partySelect.value = partyId; + syncSelect2Value(partySelect); + } + if (invoiceDateInput) invoiceDateInput.value = data.invoice_date || ''; + if (dueDateInput) dueDateInput.value = data.due_date || ''; + if (paymentTypeSelect) paymentTypeSelect.value = normalizeEditPaymentType(data.payment_type); + if (statusSelect) statusSelect.value = data.status || 'unpaid'; + if (paidAmountInput) paidAmountInput.value = parseFloat(data.paid_amount || 0).toFixed(3); + if (paidAmountContainer) { + paidAmountContainer.style.display = data.status === 'partially_paid' ? 'block' : 'none'; + } + + renderEditInvoiceItems(data.items || []); + }; + + document.querySelectorAll('.edit-invoice-btn').forEach(btn => { + btn.addEventListener('click', async function() { + const fallbackData = parseInvoiceButtonPayload(this); + const invoiceId = this.dataset.id || fallbackData.id || ''; + const type = this.dataset.type || fallbackData.type || invoiceType || 'sale'; + const tableBody = document.getElementById('editInvoiceItemsTableBody'); + + if (Object.keys(fallbackData).length > 0) { + populateEditInvoiceModal(fallbackData); + } else if (tableBody) { + tableBody.innerHTML = '
Loading invoice...'; + } + + if (!invoiceId) return; + + try { + const resp = await fetch(`index.php?action=get_invoice_details&invoice_id=${encodeURIComponent(invoiceId)}&type=${encodeURIComponent(type)}`); + const data = await resp.json(); + + if (data && data.id) { + populateEditInvoiceModal(data); + } else if (data && data.error) { + throw new Error(data.error); + } + } catch (error) { + console.error('Failed to load invoice details for edit modal.', error); + if (window.Swal) { + Swal.fire('Error', 'Failed to load invoice details', 'error'); + } + } + }); + }); + + // View and Print Invoice Logic + + } catch (e) { console.error("JS Error in DOMContentLoaded:", e); } +});