diff --git a/db/migrations/20260318_local_definitions.sql b/db/migrations/20260318_local_definitions.sql new file mode 100644 index 0000000..460baa5 --- /dev/null +++ b/db/migrations/20260318_local_definitions.sql @@ -0,0 +1,38 @@ +-- Make definitions local to outlets +-- 1. Suppliers +ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1; +ALTER TABLE suppliers ADD CONSTRAINT fk_suppliers_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE; + +-- 2. Categories +ALTER TABLE stock_categories ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1; +ALTER TABLE stock_categories ADD CONSTRAINT fk_categories_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE; + +-- 3. Units +ALTER TABLE stock_units ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1; +ALTER TABLE stock_units ADD CONSTRAINT fk_units_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE; + +-- 4. Items +ALTER TABLE stock_items ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1; +ALTER TABLE stock_items ADD CONSTRAINT fk_items_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE; + +-- 5. Fix SKU Unique Constraint (Global -> Local) +-- First, drop the global unique index if it exists. +-- We need to know the name. Usually 'sku'. We'll try to drop it. +-- MySQL might fail if index doesn't exist, so we use a procedure or just try/catch in PHP? +-- For SQL file, we can't easily do try/catch. +-- We'll try to drop 'sku' index. If it fails, it fails (user might need to run manually). +-- Safest is to just ADD the new one and let the old one be (if duplicate SKUs are allowed across outlets). +-- But we want to ALLOW duplicate SKUs across outlets. +-- So we MUST drop the unique constraint on `sku`. + +DROP INDEX sku ON stock_items; +-- Re-add as non-unique (just index) or part of composite unique +CREATE UNIQUE INDEX unique_sku_outlet ON stock_items (sku, outlet_id); + +-- 6. Migrate Stock from outlet_stock to stock_items (for Outlet 1) +-- We assume current items belong to Outlet 1. +UPDATE stock_items si +JOIN outlet_stock os ON si.id = os.item_id AND os.outlet_id = 1 +SET si.stock_quantity = os.quantity; + +-- We leave outlet_stock for now, but application logic will switch to stock_items.stock_quantity diff --git a/debug.log b/debug.log index 23d579e..78c2a95 100644 --- a/debug.log +++ b/debug.log @@ -84,3 +84,22 @@ 2026-03-18 22:32:15 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true} 2026-03-18 22:32:28 - Items case hit 2026-03-18 22:33:07 - Items case hit +2026-03-18 22:40:07 - Items case hit +2026-03-18 22:40:13 - Items case hit +2026-03-18 22:40:18 - Items case hit +2026-03-18 22:40:29 - Items case hit +2026-03-18 22:40:46 - Items case hit +2026-03-18 22:41:01 - Items case hit +2026-03-18 22:41:16 - Items case hit +2026-03-18 23:02:18 - Items case hit +2026-03-18 23:03:06 - Items case hit +2026-03-18 23:03:19 - Items case hit +2026-03-18 23:05:39 - Items case hit +2026-03-18 23:06:02 - Items case hit +2026-03-19 05:57:24 - Items case hit +2026-03-19 05:57:45 - Items case hit +2026-03-19 05:58:17 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true} +2026-03-19 05:58:34 - Items case hit +2026-03-19 05:58:51 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true} +2026-03-19 05:59:01 - Items case hit +2026-03-19 06:00:05 - Items case hit diff --git a/includes/lang.php b/includes/lang.php index ebea861..af6da82 100644 --- a/includes/lang.php +++ b/includes/lang.php @@ -95,6 +95,7 @@ $translations = [ 'cash' => 'Cash', 'card' => 'Credit Card', 'credit' => 'Credit', + 'copy_outlet_data' => 'Copy Outlet Data', ], 'ar' => [ 'dashboard' => 'لوحة القيادة', @@ -191,6 +192,7 @@ $translations = [ 'cash' => 'نقد', 'card' => 'بطاقة ائتمان', 'credit' => 'آجل', + 'copy_outlet_data' => 'نسخ بيانات الفرع', ] ]; diff --git a/includes/stock_helper.php b/includes/stock_helper.php index 0b4cca5..58d51df 100644 --- a/includes/stock_helper.php +++ b/includes/stock_helper.php @@ -3,30 +3,21 @@ if (!function_exists('current_outlet_id')) { function current_outlet_id() { - if (session_status() === PHP_SESSION_NONE) session_start(); + if (session_status() === PHP_SESSION_NONE && !headers_sent()) session_start(); return (int)($_SESSION['outlet_id'] ?? 1); } } if (!function_exists('update_stock')) { function update_stock($item_id, $qty, $outlet_id = null) { - if ($outlet_id === null) { - $outlet_id = current_outlet_id(); - } + // With local stock items, item_id is unique and holds the correct stock_quantity. + // We can update it directly. + // We ignore $outlet_id because item_id already implies the outlet (if we enforce separation). + // However, for extra safety or validation, we could check. + // But for now, simple update is best. + $db = db(); - - // 1. Update/Insert into outlet_stock (Per-Outlet Source of Truth) - $check = $db->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?"); - $check->execute([$outlet_id, $item_id]); - if (!$check->fetchColumn()) { - $db->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, 0)")->execute([$outlet_id, $item_id]); - } - - $stmt = $db->prepare("UPDATE outlet_stock SET quantity = quantity + ? WHERE outlet_id = ? AND item_id = ?"); - $stmt->execute([$qty, $outlet_id, $item_id]); - - // 2. Update global stock_items (Legacy/Aggregate Cache) - $stmtGlobal = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?"); - $stmtGlobal->execute([$qty, $item_id]); + $stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?"); + $stmt->execute([$qty, $item_id]); } -} \ No newline at end of file +} diff --git a/index.php b/index.php index 28ce68e..20c724d 100644 --- a/index.php +++ b/index.php @@ -446,7 +446,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) { exit; } $searchTerm = "%$q%"; - $oid = current_outlet_id(); $stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid WHERE (i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?) LIMIT 15"); + $oid = current_outlet_id(); $stmt = db()->prepare("SELECT i.*, i.stock_quantity FROM stock_items i WHERE (i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?) LIMIT 15"); $stmt->execute([$searchTerm, $searchTerm, $searchTerm]); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); exit; @@ -840,12 +840,10 @@ function getPromotionalPrice($item) { if (!is_dir('uploads')) mkdir('uploads', 0777, true); if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) $image_path = $filename; } - $stmt = db()->prepare("INSERT INTO stock_items (name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - // Insert 0 for global stock, real stock goes to outlet_stock - $stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, 0, $min_stock_level, $image_path, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent]); - $new_item_id = db()->lastInsertId(); $current_oid = current_outlet_id(); - db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $new_item_id, $stock_quantity]); + $stmt = db()->prepare("INSERT INTO stock_items (outlet_id, name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$current_oid, $name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $image_path, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent]); + $new_item_id = db()->lastInsertId(); redirectWithMessage("Item added successfully!"); } @@ -867,19 +865,9 @@ function getPromotionalPrice($item) { $promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null; $promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null; $promotion_percent = (float)($_POST['promotion_percent'] ?? 0); - // Update stock_items (excluding stock_quantity) - $stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, category_id = ?, unit_id = ?, supplier_id = ?, sku = ?, sale_price = ?, purchase_price = ?, min_stock_level = ?, vat_rate = ?, expiry_date = ?, is_promotion = ?, promotion_start = ?, promotion_end = ?, promotion_percent = ? WHERE id = ?"); - $stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $min_stock_level, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent, $id]); - - // Update outlet_stock - $current_oid = current_outlet_id(); - $check = db()->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?"); - $check->execute([$current_oid, $id]); - if ($check->fetch()) { - db()->prepare("UPDATE outlet_stock SET quantity = ? WHERE outlet_id = ? AND item_id = ?")->execute([$stock_quantity, $current_oid, $id]); - } else { - db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $id, $stock_quantity]); - } + // Update stock_items + $stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, category_id = ?, unit_id = ?, supplier_id = ?, sku = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, min_stock_level = ?, vat_rate = ?, expiry_date = ?, is_promotion = ?, promotion_start = ?, promotion_end = ?, promotion_percent = ? WHERE id = ?"); + $stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent, $id]); if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) { $ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION); $filename = 'uploads/item_' . $id . '_' . time() . '.' . $ext; @@ -902,11 +890,11 @@ function getPromotionalPrice($item) { db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1 AND promotion_end IS NOT NULL AND promotion_end < '" . date('Y-m-d') . "'"); if (isset($_POST['add_category'])) { - db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']); + db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, current_outlet_id())")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']); redirectWithMessage("Category added!"); } if (isset($_POST['add_unit'])) { - db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '']); + db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, current_outlet_id())")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '']); redirectWithMessage("Unit added!"); } @@ -929,7 +917,7 @@ function getPromotionalPrice($item) { if (isset($_POST['add_customer'])) { $table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers'; - $sql = "INSERT INTO $table (name, email, phone, tax_id, balance" . ($table === 'customers' ? ", loyalty_points" : "") . ") VALUES (?, ?, ?, ?, ?" . ($table === 'customers' ? ", 0" : "") . ")"; + $sql = "INSERT INTO $table (name, email, phone, tax_id, balance" . ($table === 'customers' ? ", loyalty_points" : ", outlet_id") . ") VALUES (?, ?, ?, ?, ?" . ($table === 'customers' ? ", 0" : ", " . current_outlet_id()) . ")"; db()->prepare($sql)->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0)]); redirectWithMessage("Entity added!"); } @@ -1405,6 +1393,7 @@ function getPromotionalPrice($item) { } if (isset($rows[0][0]) && stripos($rows[0][0], 'sku') !== false) array_shift($rows); + $current_oid = current_outlet_id(); foreach ($rows as $row) { if (empty($row[0])) continue; $sku = trim((string)$row[0]); @@ -1415,33 +1404,19 @@ function getPromotionalPrice($item) { $qty = (float)($row[5] ?? 0); $vat_rate = (float)($row[6] ?? 0); - $check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?"); - $check->execute([$sku]); + $check = db()->prepare("SELECT id FROM stock_items WHERE sku = ? AND outlet_id = ?"); + $check->execute([$sku, $current_oid]); $exists = $check->fetch(PDO::FETCH_ASSOC); - $current_oid = current_outlet_id(); if ($exists) { $item_id = $exists['id']; - // Update Item (excluding stock_quantity) - db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, vat_rate = ? WHERE id = ?") - ->execute([$name_en, $name_ar, $sale_price, $purchase_price, $vat_rate, $item_id]); - - // Update Outlet Stock - $os_check = db()->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?"); - $os_check->execute([$current_oid, $item_id]); - if ($os_check->fetch()) { - db()->prepare("UPDATE outlet_stock SET quantity = ? WHERE outlet_id = ? AND item_id = ?")->execute([$qty, $current_oid, $item_id]); - } else { - db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $item_id, $qty]); - } + // Update Item (including stock_quantity) + db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, vat_rate = ?, stock_quantity = ? WHERE id = ?") + ->execute([$name_en, $name_ar, $sale_price, $purchase_price, $vat_rate, $qty, $item_id]); } else { // Insert Item - db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?)") - ->execute([$sku, $name_en, $name_ar, $sale_price, $purchase_price, 0, $vat_rate]); - $item_id = db()->lastInsertId(); - - // Insert Outlet Stock - db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $item_id, $qty]); + db()->prepare("INSERT INTO stock_items (outlet_id, sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + ->execute([$current_oid, $sku, $name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate]); } $count++; } @@ -1477,7 +1452,7 @@ function getPromotionalPrice($item) { $phone = trim((string)($row[2] ?? '')); $tax_id = trim((string)($row[3] ?? '')); - db()->prepare("INSERT INTO $table (name, email, phone, tax_id, created_at) VALUES (?, ?, ?, ?, NOW())") + db()->prepare("INSERT INTO $table (name, email, phone, tax_id, created_at" . ($table === 'suppliers' ? ", outlet_id" : "") . ") VALUES (?, ?, ?, ?, NOW()" . ($table === 'suppliers' ? ", ".current_outlet_id() : "") . ")") ->execute([$name, $email, $phone, $tax_id]); $count++; } @@ -1503,7 +1478,7 @@ function getPromotionalPrice($item) { $name_en = trim((string)$row[0]); $name_ar = trim((string)($row[1] ?? $name_en)); - db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)") + db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, current_outlet_id())") ->execute([$name_en, $name_ar]); $count++; } @@ -2935,11 +2910,11 @@ if ($page === 'export') { $headers = ['LPO #', 'Supplier', 'Date', 'Total', 'Status']; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row; } elseif ($type === 'categories') { - $stmt = db()->query("SELECT id, name_en, name_ar FROM stock_categories ORDER BY id DESC"); + $stmt = db()->query("SELECT id, name_en, name_ar FROM stock_categories WHERE outlet_id = ".current_outlet_id()." ORDER BY id DESC"); $headers = ['ID', 'Name (EN)', 'Name (AR)']; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row; } elseif ($type === 'units') { - $stmt = db()->query("SELECT id, name_en, name_ar, short_name_en, short_name_ar FROM stock_units ORDER BY id DESC"); + $stmt = db()->query("SELECT id, name_en, name_ar, short_name_en, short_name_ar FROM stock_units WHERE outlet_id = ".current_outlet_id()." ORDER BY id DESC"); $headers = ['ID', 'Name (EN)', 'Name (AR)', 'Short (EN)', 'Short (AR)']; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row; } elseif ($type === 'sales_returns') { @@ -2991,9 +2966,9 @@ if ($page === 'export') { } // Global data for modals -$data['categories'] = db()->query("SELECT * FROM stock_categories ORDER BY name_en ASC")->fetchAll(); -$data['units'] = db()->query("SELECT * FROM stock_units ORDER BY name_en ASC")->fetchAll(); -$data['suppliers'] = db()->query("SELECT * FROM suppliers ORDER BY name ASC")->fetchAll(); +$data['categories'] = db()->query("SELECT * FROM stock_categories WHERE outlet_id = ".current_outlet_id()." ORDER BY name_en ASC")->fetchAll(); +$data['units'] = db()->query("SELECT * FROM stock_units WHERE outlet_id = ".current_outlet_id()." ORDER BY name_en ASC")->fetchAll(); +$data['suppliers'] = db()->query("SELECT * FROM suppliers WHERE outlet_id = ".current_outlet_id()." ORDER BY name ASC")->fetchAll(); $data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll(); $data['customers_list'] = db()->query("SELECT * FROM customers ORDER BY name ASC")->fetchAll(); $customers = $data['customers_list']; // For backward compatibility in some modals @@ -3021,7 +2996,7 @@ if ($page_num < 1) $page_num = 1; $offset = ($page_num - 1) * $limit; switch ($page) { case 'suppliers': - $where = ["1=1"]; + $where = ["outlet_id = " . current_outlet_id()]; $params = []; if (!empty($_GET['search'])) { $where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)"; @@ -3088,7 +3063,7 @@ switch ($page) { break; case 'items': file_put_contents('debug.log', date('Y-m-d H:i:s') . " - Items case hit\n", FILE_APPEND); - $where = ["1=1"]; + $where = ["i.outlet_id = " . current_outlet_id()]; $params = []; if (!empty($_GET['search'])) { $where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)"; @@ -3108,9 +3083,9 @@ switch ($page) { $data['current_page'] = $page_num; $oid = current_outlet_id(); - $stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name + $stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name FROM stock_items i - LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid + LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN stock_units u ON i.unit_id = u.id LEFT JOIN suppliers s ON i.supplier_id = s.id @@ -3250,6 +3225,7 @@ switch ($page) { } $data['outlets'] = db()->query("SELECT * FROM outlets ORDER BY id ASC")->fetchAll(); break; + case 'copy_outlet_data': require 'pages/copy_outlet_data_logic.php'; break; case 'settings': // Already fetched globally break; @@ -3334,7 +3310,7 @@ switch ($page) { } unset($inv); - $oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, COALESCE(os.quantity, 0) as stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC); + $oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, i.stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC); foreach ($items_list_raw as &$item) { $item['sale_price'] = getPromotionalPrice($item); } @@ -3544,13 +3520,13 @@ switch ($page) { break; case 'low_stock_report': $oid = current_outlet_id(); - $stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name + $stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name FROM stock_items i - LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid + LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN suppliers s ON i.supplier_id = s.id - WHERE COALESCE(os.quantity, 0) <= i.min_stock_level - ORDER BY (i.min_stock_level - COALESCE(os.quantity, 0)) DESC"); + WHERE i.outlet_id = $oid AND i.stock_quantity <= i.min_stock_level + ORDER BY (i.min_stock_level - i.stock_quantity) DESC"); $stmt->execute(); $data['low_stock_items'] = $stmt->fetchAll(); break; @@ -3697,7 +3673,7 @@ switch ($page) { $pay_pur_cond = ($current_oid > 0) ? " WHERE p.outlet_id = $current_oid " : ""; $low_stock_query = ($current_oid > 0) - ? "SELECT COUNT(*) FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $current_oid WHERE COALESCE(os.quantity, 0) <= i.min_stock_level" + ? "SELECT COUNT(*) FROM stock_items WHERE outlet_id = $current_oid AND stock_quantity <= min_stock_level" : "SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level"; $data['stats'] = [ @@ -3728,7 +3704,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
-