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