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 = '