diff --git a/assets/pasted-20260219-104108-ada41fa8.png b/assets/pasted-20260219-104108-ada41fa8.png new file mode 100644 index 0000000..ea98252 Binary files /dev/null and b/assets/pasted-20260219-104108-ada41fa8.png differ diff --git a/db/migrations/20260219_add_vat_to_pos_items.sql b/db/migrations/20260219_add_vat_to_pos_items.sql new file mode 100644 index 0000000..fb41ccf --- /dev/null +++ b/db/migrations/20260219_add_vat_to_pos_items.sql @@ -0,0 +1,16 @@ +-- Add VAT columns to pos_items +ALTER TABLE pos_items ADD COLUMN vat_rate DECIMAL(5,2) DEFAULT 0.00 AFTER unit_price; +ALTER TABLE pos_items ADD COLUMN vat_amount DECIMAL(15,3) DEFAULT 0.000 AFTER vat_rate; + +-- Update existing pos_items if possible (optional, but good for consistency) +-- This might not be accurate if items changed their vat rate after transaction, +-- but it's better than nothing. +UPDATE pos_items pi +JOIN stock_items si ON pi.product_id = si.id +SET pi.vat_rate = si.vat_rate; + +UPDATE pos_items SET vat_amount = subtotal * (vat_rate / (100 + vat_rate)); + +-- Update pos_transactions to ensure tax_amount is populated for old transactions +UPDATE pos_transactions pt +SET tax_amount = (SELECT SUM(vat_amount) FROM pos_items WHERE transaction_id = pt.id); diff --git a/debug_sessions.log b/debug_sessions.log new file mode 100644 index 0000000..b435860 --- /dev/null +++ b/debug_sessions.log @@ -0,0 +1 @@ +Where: 1=1 Params: [] diff --git a/includes/lang.php b/includes/lang.php index 2e57fdd..72e1d15 100644 --- a/includes/lang.php +++ b/includes/lang.php @@ -90,7 +90,7 @@ $translations = [ 'walk_in_customer' => 'Walk-in Customer', 'currency' => 'ر.ع / OMR', 'cash' => 'Cash', - 'card' => 'Card', + 'card' => 'Credit Card', 'credit' => 'Credit', ], 'ar' => [ @@ -183,7 +183,7 @@ $translations = [ 'walk_in_customer' => 'عميل نقدي', 'currency' => 'ر.ع / OMR', 'cash' => 'نقد', - 'card' => 'بطاقة', + 'card' => 'بطاقة ائتمان', 'credit' => 'آجل', ] ]; diff --git a/index.php b/index.php index 9b14ede..18b94dc 100644 --- a/index.php +++ b/index.php @@ -258,6 +258,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) { $payments = json_decode($_POST['payments'] ?? '[]', true); $items = json_decode($_POST['items'] ?? '[]', true); $total_amount = (float)($_POST['total_amount'] ?? 0); + $tax_amount = (float)($_POST['tax_amount'] ?? 0); $discount_code_id = !empty($_POST['discount_code_id']) ? (int)$_POST['discount_code_id'] : null; $discount_amount = (float)($_POST['discount_amount'] ?? 0); $loyalty_redeemed = (float)($_POST['loyalty_redeemed'] ?? 0); @@ -266,18 +267,30 @@ if (isset($_GET['action']) || isset($_POST['action'])) { $transaction_no = 'POS-' . time() . rand(10, 99); $session_id = $_SESSION['register_session_id'] ?? null; + if (!$session_id) { + // Fallback: try to find an open session for this user + $check_session = $db->prepare("SELECT id FROM register_sessions WHERE user_id = ? AND status = 'open' LIMIT 1"); + $check_session->execute([$_SESSION['user_id']]); + $session_id = $check_session->fetchColumn() ?: null; + if ($session_id) { + $_SESSION['register_session_id'] = $session_id; + } + } + // Insert Transaction - $stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, discount_code_id, discount_amount, loyalty_points_redeemed, net_amount, register_session_id, created_by, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed')"); - $stmt->execute([$transaction_no, $customer_id, $total_amount, $discount_code_id, $discount_amount, $loyalty_redeemed, $net_amount, $session_id, $_SESSION['user_id']]); + $stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, tax_amount, discount_code_id, discount_amount, loyalty_points_redeemed, net_amount, register_session_id, created_by, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed')"); + $stmt->execute([$transaction_no, $customer_id, $total_amount, $tax_amount, $discount_code_id, $discount_amount, $loyalty_redeemed, $net_amount, $session_id, $_SESSION['user_id']]); $transaction_id = (int)$db->lastInsertId(); // Insert Items & Update Stock - $stmtItem = $db->prepare("INSERT INTO pos_items (transaction_id, product_id, quantity, unit_price, subtotal) VALUES (?, ?, ?, ?, ?)"); + $stmtItem = $db->prepare("INSERT INTO pos_items (transaction_id, product_id, quantity, unit_price, vat_rate, vat_amount, subtotal) VALUES (?, ?, ?, ?, ?, ?, ?)"); $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?"); foreach ($items as $item) { $sub = (float)$item['price'] * (float)$item['qty']; - $stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $sub]); + $vr = (float)($item['vat_rate'] ?? 0); + $va = (float)($item['vat_amount'] ?? 0); + $stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $vr, $va, $sub]); $stmtStock->execute([$item['qty'], $item['id']]); } @@ -1911,10 +1924,30 @@ switch ($page) { case 'register_sessions': $where = ["1=1"]; $params = []; + + // Filter by user if provided and user has permission + if (isset($_GET['user_id']) && !empty($_GET['user_id'])) { + if (can('users_view')) { + $where[] = "s.user_id = ?"; + $params[] = $_GET['user_id']; + } + } + if (!can('users_view')) { $where[] = "s.user_id = ?"; $params[] = $_SESSION['user_id']; } + + // Filter by date range + if (isset($_GET['date_from']) && !empty($_GET['date_from'])) { + $where[] = "s.opened_at >= ?"; + $params[] = $_GET['date_from'] . ' 00:00:00'; + } + if (isset($_GET['date_to']) && !empty($_GET['date_to'])) { + $where[] = "s.opened_at <= ?"; + $params[] = $_GET['date_to'] . ' 23:59:59'; + } + $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT s.*, r.name as register_name, u.username FROM register_sessions s @@ -1925,6 +1958,7 @@ switch ($page) { $stmt->execute($params); $data['sessions'] = $stmt->fetchAll(); $data['cash_registers'] = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll(); + $data['users'] = db()->query("SELECT id, username FROM users ORDER BY username ASC")->fetchAll(); break; default: $data['customers'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY id DESC LIMIT 5")->fetchAll(); @@ -2802,7 +2836,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; - + item @@ -2839,7 +2873,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
- +
@@ -2874,7 +2908,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
@@ -3376,7 +3410,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; const totalVat = this.items.reduce((sum, item) => { const price = parseFloat(item.price) || 0; const qty = parseFloat(item.qty) || 0; - const vatRate = item.vatRate || 5; + const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 5; return sum + (price * qty * (vatRate / (100 + vatRate))); }, 0); let discountAmount = 0; @@ -3395,7 +3429,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; items: this.items.map(i => { const price = parseFloat(i.price) || 0; const qty = parseFloat(i.qty) || 0; - const vatRate = i.vatRate || 5; + const vatRate = (i.vatRate !== undefined && i.vatRate !== null) ? i.vatRate : 5; const vatAmount = price * qty * (vatRate / (100 + vatRate)); return { name: (document.documentElement.lang === 'ar' ? (i.nameAr || i.nameEn) : (i.nameEn || i.nameAr)) || 'Unknown Item', @@ -3680,7 +3714,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; const qty = parseFloat(item.qty) || 0; const itemTotal = price * qty; subtotal += itemTotal; - const vatRate = item.vatRate || 5; + const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 5; const itemVat = itemTotal * (vatRate / (100 + vatRate)); totalVat += itemVat; const displayName = (lang === 'ar' ? (item.nameAr || item.nameEn) : (item.nameEn || item.nameAr)) || 'Unknown Item'; @@ -3850,10 +3884,16 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; }, renderPayments() { const container = document.getElementById('paymentList'); + const methodLabels = { + 'cash': 'Cash', + 'card': 'Credit Card', + 'credit': 'Credit', + 'transfer': 'Bank Transfer' + }; container.innerHTML = this.payments.map((p, i) => `
- ${p.method} + ${methodLabels[p.method] || p.method} ${p.amount.toFixed(3)}
+ + Clear + +
+ + + + +
@@ -7040,14 +7133,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; - - - + + - - - - + + @@ -7082,11 +7172,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; ?> - - - + + + + +
Opened At Closed At Opening Bal.Cash SalesCard SalesTransferCash SaleCredit Card CreditTotal SalesExpected CashActual CashShortageTotal SaleBalance Status Report
OMR OMR OMR OMR OMR OMR OMR - - -
+
+ + + + + + @@ -7225,9 +7337,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; Time - Order # - Customer - Method + Order # + Customer + Items + Method Amount @@ -7240,9 +7353,19 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; ?> - - - + + + + prepare("SELECT i.name_en, ti.quantity FROM pos_items ti JOIN stock_items i ON ti.item_id = i.id WHERE ti.transaction_id = ?"); + $items_stmt->execute([$tx['id']]); + $items = $items_stmt->fetchAll(); + foreach ($items as $item) { + echo "" . htmlspecialchars($item['name_en']) . " x " . (float)$item['quantity'] . ""; + } + ?> + + @@ -7272,13 +7395,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; - - - - - - - + @@ -10044,7 +10161,7 @@ document.addEventListener('DOMContentLoaded', function() {
- Card + Credit Card
@@ -10052,7 +10169,7 @@ document.addEventListener('DOMContentLoaded', function() {
- Transfer + Bank Transfer
@@ -10385,7 +10502,7 @@ document.addEventListener('DOMContentLoaded', function() { const uniqueId = Math.random().toString(36).substr(2, 9); const svgId = `bc-${sku}-${uniqueId}`; label.innerHTML = ` -
${name}
+
${name}
OMR ${price}
`; @@ -10493,7 +10610,7 @@ document.addEventListener('DOMContentLoaded', function() { padding: 1mm; } .label-container:last-child { page-break-after: avoid; } - .label-name { font-size: 10px; font-weight: bold; margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } + .label-name { font-size: 9px; font-weight: bold; margin-bottom: 2px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } .label-price { font-size: 12px; font-weight: bold; margin-top: 2px; } svg { max-width: 100%; height: auto; max-height: 70%; display: block; } @@ -10629,7 +10746,7 @@ document.addEventListener('DOMContentLoaded', function() { const container = document.getElementById('posReceiptContent'); const itemsHtml = inv.items.map(item => { const itemTotal = item.unit_price * item.quantity; - const vatRate = parseFloat(item.vat_rate || 5); + const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 5); const vatAmount = itemTotal * (vatRate / (100 + vatRate)); return ` @@ -10642,7 +10759,7 @@ document.addEventListener('DOMContentLoaded', function() { const totalVat = inv.items.reduce((sum, item) => { const itemTotal = item.unit_price * item.quantity; - const vatRate = parseFloat(item.vat_rate || 5); + const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 5); return sum + (itemTotal * (vatRate / (100 + vatRate))); }, 0); const subtotal = inv.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0); diff --git a/post_debug.log b/post_debug.log index 0281ed3..1e51d2b 100644 --- a/post_debug.log +++ b/post_debug.log @@ -74,3 +74,18 @@ 2026-02-19 07:58:34 - POST: {"open_register":"1","register_id":"1","opening_balance":"11"} 2026-02-19 07:58:46 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.845}]","total_amount":"0.8450000000000001","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":3,\"qty\":1,\"price\":0.25},{\"id\":2,\"qty\":1,\"price\":0.2125}]"} 2026-02-19 07:59:08 - POST: {"close_register":"1","session_id":"7","cash_in_hand":"13","notes":""} +2026-02-19 08:20:34 - POST: {"open_register":"1","register_id":"1","opening_balance":"12"} +2026-02-19 08:20:44 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.845}]","total_amount":"0.8450000000000001","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":3,\"qty\":1,\"price\":0.25},{\"id\":2,\"qty\":1,\"price\":0.2125}]"} +2026-02-19 08:20:57 - POST: {"close_register":"1","session_id":"8","cash_in_hand":"14","notes":""} +2026-02-19 10:20:51 - POST: {"open_register":"1","register_id":"1","opening_balance":"22"} +2026-02-19 10:21:00 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.845}]","total_amount":"0.8450000000000001","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":3,\"qty\":1,\"price\":0.25},{\"id\":2,\"qty\":1,\"price\":0.2125}]"} +2026-02-19 10:21:15 - POST: {"close_register":"1","session_id":"9","cash_in_hand":"10","notes":""} +2026-02-19 10:50:12 - POST: {"open_register":"1","register_id":"2","opening_balance":"10"} +2026-02-19 10:50:49 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.845}]","total_amount":"0.8450000000000001","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":3,\"qty\":1,\"price\":0.25},{\"id\":2,\"qty\":1,\"price\":0.2125}]"} +2026-02-19 10:51:45 - POST: {"id":"3","name_en":"Tissue","name_ar":"\u0645\u062d\u0627\u0631\u0645 \u0648\u0631\u0642\u064a\u0629","category_id":"2","unit_id":"2","supplier_id":"6","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"-4","min_stock_level":"0","sku":"760115926272","vat_rate":"0","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""} +2026-02-19 10:52:25 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.845}]","total_amount":"0.8450000000000001","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":3,\"qty\":1,\"price\":0.25},{\"id\":2,\"qty\":1,\"price\":0.2125}]"} +2026-02-19 10:54:21 - POST: {"open_register":"1","register_id":"1","opening_balance":"5"} +2026-02-19 10:54:24 - POST: {"open_register":"1","register_id":"1","opening_balance":"5"} +2026-02-19 10:54:25 - POST: {"open_register":"1","register_id":"2","opening_balance":"5"} +2026-02-19 10:54:25 - POST: {"open_register":"1","register_id":"2","opening_balance":"5"} +2026-02-19 10:54:26 - POST: {"open_register":"1","register_id":"2","opening_balance":"5"}