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';
- |
+ |
@@ -2839,7 +2873,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}
= __('currency') ?> ${p.amount.toFixed(3)}
- |
-
-
-
-
-
-
+
@@ -9273,7 +9390,7 @@ document.addEventListener('DOMContentLoaded', function() {
@@ -9373,7 +9490,7 @@ document.addEventListener('DOMContentLoaded', function() {
@@ -9900,7 +10017,7 @@ document.addEventListener('DOMContentLoaded', function() {
@@ -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"}