diff --git a/assets/pasted-20260216-140816-e8c8b7ad.png b/assets/pasted-20260216-140816-e8c8b7ad.png new file mode 100644 index 0000000..2d71ca5 Binary files /dev/null and b/assets/pasted-20260216-140816-e8c8b7ad.png differ diff --git a/assets/pasted-20260216-153830-2a6e7f2f.png b/assets/pasted-20260216-153830-2a6e7f2f.png new file mode 100644 index 0000000..582c9ba Binary files /dev/null and b/assets/pasted-20260216-153830-2a6e7f2f.png differ diff --git a/db/migrations/20260216_add_credit_limit.sql b/db/migrations/20260216_add_credit_limit.sql new file mode 100644 index 0000000..541ed76 --- /dev/null +++ b/db/migrations/20260216_add_credit_limit.sql @@ -0,0 +1 @@ +ALTER TABLE customers ADD COLUMN credit_limit DECIMAL(15,3) DEFAULT 0.000 AFTER balance; diff --git a/index.php b/index.php index 660bbd5..fbd76c9 100644 --- a/index.php +++ b/index.php @@ -169,15 +169,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } + // Calculate actual paid amount (excluding credit) + $actual_paid = 0; + foreach ($payments as $p) { + if ($p['method'] !== 'credit') { + $actual_paid += (float)$p['amount']; + } + } + + $status = 'paid'; + if ($actual_paid <= 0) { + $status = 'unpaid'; + } elseif ($actual_paid < $net_amount - 0.001) { + $status = 'partially_paid'; + } + + $methods_str = implode(', ', array_unique(array_map(fn($p) => $p['method'], $payments))); + // Create Invoice - $stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, total_with_vat, paid_amount, type) VALUES (?, CURDATE(), 'paid', ?, ?, 'sale')"); - $stmt->execute([$customer_id, $net_amount, $net_amount]); + $stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, total_with_vat, paid_amount, type, payment_type) VALUES (?, CURDATE(), ?, ?, ?, 'sale', ?)"); + $stmt->execute([$customer_id, $status, $net_amount, $actual_paid, $methods_str]); $invoice_id = $db->lastInsertId(); // Add POS Transaction record $stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, discount_code_id, discount_amount, loyalty_points_earned, loyalty_points_redeemed, net_amount, payment_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); $transaction_no = 'POS-' . time() . rand(100, 999); - $methods_str = implode(', ', array_unique(array_map(fn($p) => $p['method'], $payments))); $stmt->execute([$transaction_no, $customer_id, $total_amount, $discount_code_id, $discount_amount, $loyalty_earned, $loyalty_redeemed, $net_amount, $methods_str]); $pos_id = $db->lastInsertId(); @@ -214,6 +230,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Add Payments foreach ($payments as $p) { + if ($p['method'] === 'credit') continue; $stmt = $db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, CURDATE(), ?, 'POS Transaction')"); $stmt->execute([$invoice_id, (float)$p['amount'], $p['method']]); } @@ -979,13 +996,17 @@ switch ($page) { } $whereSql = implode(" AND ", $where); - $stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id + $stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id, c.phone as customer_phone FROM invoices v LEFT JOIN customers c ON v.customer_id = c.id WHERE $whereSql ORDER BY v.id DESC"); $stmt->execute($params); $data['invoices'] = $stmt->fetchAll(); + foreach ($data['invoices'] as &$inv) { + $inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']); + } + unset($inv); $data['items_list'] = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items ORDER BY name_en ASC")->fetchAll(); $data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = '" . ($type === 'sale' ? 'customer' : 'supplier') . "' ORDER BY name ASC")->fetchAll(); @@ -1060,42 +1081,16 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + + + @@ -1759,6 +1754,16 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; customerPoints: 0, selectedPaymentMethod: 'cash', payments: [], + allCreditCustomers: (string)$c['id'], + 'text' => $c['name'] . ' (' . ($c['phone'] ?? '') . ')' + ]; + } + echo json_encode($custData); + ?>, add(product) { const existing = this.items.find(item => item.id === product.id); if (existing) { @@ -2010,9 +2015,6 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; this.payments = []; this.renderPayments(); - if (document.getElementById('creditCustomerSearch')) { - document.getElementById('creditCustomerSearch').value = ''; - } document.getElementById('paymentAmountDue').innerText = 'OMR ' + total.toFixed(3); document.getElementById('partialAmount').value = total.toFixed(3); @@ -2020,8 +2022,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; const creditSection = document.getElementById('creditCustomerSection'); if (this.selectedPaymentMethod === 'credit') { creditSection.style.display = 'block'; - this.filterCreditCustomers(); - document.getElementById('paymentCreditCustomer').value = customerSelect.value; + const creditSelect = $('#paymentCreditCustomer'); + creditSelect.val(customerSelect.value).trigger('change'); } else { creditSection.style.display = 'none'; } @@ -2039,12 +2041,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; const creditSection = document.getElementById('creditCustomerSection'); if (method === 'credit') { creditSection.style.display = 'block'; - if (document.getElementById('creditCustomerSearch')) { - document.getElementById('creditCustomerSearch').value = ''; - } - this.filterCreditCustomers(); // Sync with main customer select - document.getElementById('paymentCreditCustomer').value = document.getElementById('posCustomer').value; + const creditSelect = $('#paymentCreditCustomer'); + creditSelect.val(document.getElementById('posCustomer').value).trigger('change'); } else { creditSection.style.display = 'none'; } @@ -2308,37 +2307,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; location.reload(); }, { once: true }); }, - filterCreditCustomers() { - const searchInput = document.getElementById('creditCustomerSearch'); - if (!searchInput) return; - const search = searchInput.value.toLowerCase(); - const select = document.getElementById('paymentCreditCustomer'); - if (!select) return; - - if (!this.allCreditCustomers || (this.allCreditCustomers.length <= 1 && select.options.length > 1)) { - this.allCreditCustomers = Array.from(select.options).map(opt => ({ - value: opt.value, - text: opt.text, - search: opt.getAttribute('data-search') || opt.text.toLowerCase() - })); - } - - if (!this.allCreditCustomers) return; - - const currentValue = select.value; - select.innerHTML = ''; - - this.allCreditCustomers.forEach(opt => { - if (opt.value === "" || opt.search.includes(search)) { - const o = document.createElement('option'); - o.value = opt.value; - o.text = opt.text; - o.setAttribute('data-search', opt.search); - if (opt.value === currentValue) o.selected = true; - select.appendChild(o); - } - }); - }, + async syncCustomer(val) { document.getElementById('posCustomer').value = val; const customerSelect = document.getElementById('posCustomer'); @@ -2417,6 +2386,18 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; if (bc) bc.focus(); } }); + + $(document).ready(function() { + $('#posCustomer').select2({ + width: '100%', + placeholder: 'Select Customer' + }); + $('#paymentCreditCustomer').select2({ + width: '100%', + placeholder: 'Select Customer', + dropdownParent: $('#posPaymentModal') + }); + }); @@ -2508,12 +2489,12 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; OMR
- - + + - +
@@ -3408,108 +3389,22 @@ document.addEventListener('DOMContentLoaded', function() { }); }); - // View Invoice Logic - document.querySelectorAll('.view-invoice-btn').forEach(btn => { - btn.addEventListener('click', function() { - const data = JSON.parse(this.dataset.json); - document.getElementById('invNumber').textContent = 'INV-' + data.id.toString().padStart(5, '0'); - 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 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'; + // 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); } - - 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.charAt(0).toUpperCase() + data.status.slice(1); - let statusAr = data.status; - - if (data.status === 'paid') { - statusClass = 'bg-success'; - statusAr = 'مدفوع'; - } else if (data.status === 'unpaid') { - statusClass = 'bg-danger'; - statusAr = 'غير مدفوع'; - } else if (data.status === 'partially_paid') { - statusClass = 'bg-warning text-dark'; - statusEn = 'Partially Paid'; - statusAr = 'مدفوع جزئياً'; + } + 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); } - - statusLabel.textContent = statusEn; - statusLabel.setAttribute('data-en', statusEn); - statusLabel.setAttribute('data-ar', statusAr); - statusLabel.className = 'badge text-uppercase ' + statusClass; - - const body = document.getElementById('invItemsBody'); - body.innerHTML = ''; - 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(3)}% - OMR ${parseFloat(item.total_price).toFixed(3)} - `; - body.appendChild(tr); - }); - document.getElementById('invSubtotal').textContent = 'OMR ' + parseFloat(data.total_amount).toFixed(3); - document.getElementById('invVatAmount').textContent = 'OMR ' + (parseFloat(data.vat_amount) || 0).toFixed(3); - const grandTotalValue = (parseFloat(data.total_with_vat) || parseFloat(data.total_amount)); - document.getElementById('invGrandTotal').textContent = 'OMR ' + grandTotalValue.toFixed(3); - - // Payment Info - document.getElementById('invTotalInfo').textContent = 'OMR ' + grandTotalValue.toFixed(3); - document.getElementById('invPaidInfo').textContent = 'OMR ' + parseFloat(data.paid_amount || 0).toFixed(3); - const balance = grandTotalValue - parseFloat(data.paid_amount || 0); - document.getElementById('invBalanceInfo').textContent = 'OMR ' + balance.toFixed(3); - - // Fetch and show payment history - const paymentsBody = document.getElementById('invPaymentsBody'); - const paymentsSection = document.getElementById('invPaymentsSection'); - paymentsBody.innerHTML = ''; - - fetch(`index.php?action=get_payments&invoice_id=${data.id}`) - .then(res => res.json()) - .then(payments => { - if (payments && payments.length > 0) { - 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); - }); - paymentsSection.style.display = 'block'; - } else { - paymentsSection.style.display = 'none'; - } - }); - - // Generate QR Code - const companyName = ""; - const vatNo = ""; - const total = (parseFloat(data.total_with_vat) || parseFloat(data.total_amount)).toFixed(3); - const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: INV-${data.id.toString().padStart(5, '0')}\nDate: ${data.invoice_date}\nTotal: ${total}`; - const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrData)}`; - document.getElementById('invQrCode').innerHTML = `QR Code`; - }); + } }); }); @@ -3717,91 +3612,174 @@ document.addEventListener('DOMContentLoaded', function() {
+ +