diff --git a/admin/ads.php b/admin/ads.php index 895236c..69a4d96 100644 --- a/admin/ads.php +++ b/admin/ads.php @@ -76,20 +76,28 @@ if (isset($_GET['delete'])) { if (!has_permission('ads')) { $message = '
Access Denied.
'; } else { - $id = $_GET['delete']; - $stmt = $pdo->prepare("SELECT image_path FROM ads_images WHERE id = ?"); - $stmt->execute([$id]); - $promo = $stmt->fetch(); - if ($promo) { - $fullPath = __DIR__ . '/../' . $promo['image_path']; - if (file_exists($fullPath) && is_file($fullPath)) unlink($fullPath); - $pdo->prepare("DELETE FROM ads_images WHERE id = ?")->execute([$id]); + try { + $id = $_GET['delete']; + $stmt = $pdo->prepare("SELECT image_path FROM ads_images WHERE id = ?"); + $stmt->execute([$id]); + $promo = $stmt->fetch(); + if ($promo) { + $fullPath = __DIR__ . '/../' . $promo['image_path']; + if (file_exists($fullPath) && is_file($fullPath)) unlink($fullPath); + $pdo->prepare("DELETE FROM ads_images WHERE id = ?")->execute([$id]); + } + header("Location: ads.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error deleting advertisement: ' . $e->getMessage() . '
'; } - header("Location: ads.php"); - exit; } } +if (isset($_GET['deleted'])) { + $message = '
Advertisement deleted successfully!
'; +} + $query = "SELECT * FROM ads_images ORDER BY sort_order ASC, created_at DESC"; $promos_pagination = paginate_query($pdo, $query); $promos = $promos_pagination['data']; @@ -278,4 +286,4 @@ function preparePromoEditForm(data) { - \ No newline at end of file + diff --git a/admin/areas.php b/admin/areas.php index cce2a30..796900f 100644 --- a/admin/areas.php +++ b/admin/areas.php @@ -40,23 +40,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('areas_del')) { $message = '
Access Denied: You do not have permission to delete areas.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM areas WHERE id = ?")->execute([$id]); - header("Location: areas.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Soft delete to preserve relations with tables + $pdo->prepare("UPDATE areas SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: areas.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing area: ' . $e->getMessage() . '
'; + } } } -$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name ASC")->fetchAll(); +if (isset($_GET['deleted'])) { + $message = '
Area removed successfully!
'; +} + +$outlets = $pdo->query("SELECT * FROM outlets WHERE is_deleted = 0 ORDER BY name ASC")->fetchAll(); $query = "SELECT a.*, o.name as outlet_name FROM areas a LEFT JOIN outlets o ON a.outlet_id = o.id + WHERE a.is_deleted = 0 ORDER BY a.id DESC"; $areas_pagination = paginate_query($pdo, $query); $areas = $areas_pagination['data']; @@ -104,7 +114,7 @@ include 'includes/header.php'; - + diff --git a/admin/categories.php b/admin/categories.php index 79f760c..54b0272 100644 --- a/admin/categories.php +++ b/admin/categories.php @@ -60,19 +60,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('categories_del')) { $message = '
Access Denied: You do not have permission to delete categories.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM categories WHERE id = ?")->execute([$id]); - header("Location: categories.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Soft delete to avoid breaking product relations and historical order integrity + $pdo->prepare("UPDATE categories SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: categories.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing category: ' . $e->getMessage() . '
'; + } } } -$query = "SELECT * FROM categories ORDER BY name ASC"; +if (isset($_GET['deleted'])) { + $message = '
Category removed successfully!
'; +} + +$query = "SELECT * FROM categories WHERE is_deleted = 0 ORDER BY name ASC"; $categories_pagination = paginate_query($pdo, $query); $categories = $categories_pagination['data']; @@ -135,7 +144,7 @@ include 'includes/header.php'; - + diff --git a/admin/customers.php b/admin/customers.php index ef3160d..1b8f3a9 100644 --- a/admin/customers.php +++ b/admin/customers.php @@ -48,13 +48,25 @@ if (isset($_GET['delete'])) { if (!has_permission('customers_del')) { $message = '
Access Denied: You do not have permission to delete customers.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM customers WHERE id = ?")->execute([$id]); - header("Location: customers.php"); - exit; + try { + $id = $_GET['delete']; + $pdo->prepare("DELETE FROM customers WHERE id = ?")->execute([$id]); + header("Location: customers.php?deleted=1"); + exit; + } catch (PDOException $e) { + if ($e->getCode() == '23000') { + $message = '
Cannot delete this customer because they are linked to other records (e.g., orders).
'; + } else { + $message = '
Error deleting customer: ' . $e->getMessage() . '
'; + } + } } } +if (isset($_GET['deleted'])) { + $message = '
Customer deleted successfully!
'; +} + $search = $_GET['search'] ?? ''; $params = []; $query = "SELECT * FROM customers"; @@ -202,7 +214,7 @@ include 'includes/header.php'; @@ -231,4 +243,4 @@ function prepareEditForm(customer) { - \ No newline at end of file + diff --git a/admin/expense_categories.php b/admin/expense_categories.php index cfcfd31..da2b9f0 100644 --- a/admin/expense_categories.php +++ b/admin/expense_categories.php @@ -41,19 +41,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('expense_categories_del')) { $message = '
Access Denied: You do not have permission to delete expense categories.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM expense_categories WHERE id = ?")->execute([$id]); - header("Location: expense_categories.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Soft delete to preserve relations with expenses + $pdo->prepare("UPDATE expense_categories SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: expense_categories.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing category: ' . $e->getMessage() . '
'; + } } } -$query = "SELECT * FROM expense_categories ORDER BY name ASC"; +if (isset($_GET['deleted'])) { + $message = '
Expense category removed successfully!
'; +} + +$query = "SELECT * FROM expense_categories WHERE is_deleted = 0 ORDER BY name ASC"; $expense_categories_pagination = paginate_query($pdo, $query); $expense_categories = $expense_categories_pagination['data']; @@ -102,7 +111,7 @@ include 'includes/header.php'; - + diff --git a/admin/expenses.php b/admin/expenses.php index 8f392f7..495719d 100644 --- a/admin/expenses.php +++ b/admin/expenses.php @@ -45,14 +45,18 @@ if (isset($_GET['delete'])) { if (!has_permission('expenses_del')) { $message = '
Access Denied: You do not have permission to delete expenses.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM expenses WHERE id = ?")->execute([$id]); - header("Location: expenses.php?success=deleted"); - exit; + try { + $id = $_GET['delete']; + $pdo->prepare("DELETE FROM expenses WHERE id = ?")->execute([$id]); + header("Location: expenses.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error deleting expense: ' . $e->getMessage() . '
'; + } } } -if (isset($_GET['success']) && $_GET['success'] === 'deleted') { +if (isset($_GET['deleted'])) { $message = '
Expense deleted successfully!
'; } @@ -288,4 +292,4 @@ function editExpense(exp) { } - \ No newline at end of file + diff --git a/admin/order_view.php b/admin/order_view.php index bcd2c4c..ae74de3 100644 --- a/admin/order_view.php +++ b/admin/order_view.php @@ -30,9 +30,9 @@ if (!$order) { } // Fetch Order Items -$stmt = $pdo->prepare("SELECT oi.*, p.name as product_name, pv.name as variant_name +$stmt = $pdo->prepare("SELECT oi.*, COALESCE(p.name, oi.product_name) as product_name, COALESCE(pv.name, oi.variant_name) as variant_name FROM order_items oi - JOIN products p ON oi.product_id = p.id + LEFT JOIN products p ON oi.product_id = p.id LEFT JOIN product_variants pv ON oi.variant_id = pv.id WHERE oi.order_id = ?"); $stmt->execute([$id]); diff --git a/admin/outlets.php b/admin/outlets.php index 7483fc3..91fa4fd 100644 --- a/admin/outlets.php +++ b/admin/outlets.php @@ -41,19 +41,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('outlets_del')) { $message = '
Access Denied: You do not have permission to delete outlets.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM outlets WHERE id = ?")->execute([$id]); - header("Location: outlets.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Soft delete to preserve relations with users, expenses, and orders + $pdo->prepare("UPDATE outlets SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: outlets.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing outlet: ' . $e->getMessage() . '
'; + } } } -$query = "SELECT * FROM outlets ORDER BY id DESC"; +if (isset($_GET['deleted'])) { + $message = '
Outlet removed successfully!
'; +} + +$query = "SELECT * FROM outlets WHERE is_deleted = 0 ORDER BY id DESC"; $outlets_pagination = paginate_query($pdo, $query); $outlets = $outlets_pagination['data']; @@ -103,7 +112,7 @@ include 'includes/header.php'; - + diff --git a/admin/payment_types.php b/admin/payment_types.php index b04d81b..46bbe74 100644 --- a/admin/payment_types.php +++ b/admin/payment_types.php @@ -50,13 +50,25 @@ if (isset($_GET['delete'])) { if (!has_permission('payment_types_del')) { $message = '
Access Denied: You do not have permission to delete payment types.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM payment_types WHERE id = ?")->execute([$id]); - header("Location: payment_types.php"); - exit; + try { + $id = $_GET['delete']; + $pdo->prepare("DELETE FROM payment_types WHERE id = ?")->execute([$id]); + header("Location: payment_types.php?deleted=1"); + exit; + } catch (PDOException $e) { + if ($e->getCode() == '23000') { + $message = '
Cannot delete this payment type because it is linked to other records (e.g., orders).
'; + } else { + $message = '
Error deleting payment type: ' . $e->getMessage() . '
'; + } + } } } +if (isset($_GET['deleted'])) { + $message = '
Payment type deleted successfully!
'; +} + $query = "SELECT * FROM payment_types ORDER BY id ASC"; $payments_pagination = paginate_query($pdo, $query); $payment_types = $payments_pagination['data']; @@ -202,4 +214,4 @@ function openEditModal(type) { - + \ No newline at end of file diff --git a/admin/product_variants.php b/admin/product_variants.php index 47dd030..38fb623 100644 --- a/admin/product_variants.php +++ b/admin/product_variants.php @@ -20,16 +20,22 @@ if (!$product) { exit; } +$message = ''; + // Handle Add Variant if (isset($_POST['action']) && $_POST['action'] === 'add_variant') { $name = $_POST['name']; $name_ar = $_POST['name_ar'] ?? ''; $price_adj = $_POST['price_adjustment']; - $stmt = $pdo->prepare("INSERT INTO product_variants (product_id, name, name_ar, price_adjustment) VALUES (?, ?, ?, ?)"); - $stmt->execute([$product_id, $name, $name_ar, $price_adj]); - header("Location: product_variants.php?product_id=$product_id"); - exit; + try { + $stmt = $pdo->prepare("INSERT INTO product_variants (product_id, name, name_ar, price_adjustment) VALUES (?, ?, ?, ?)"); + $stmt->execute([$product_id, $name, $name_ar, $price_adj]); + header("Location: product_variants.php?product_id=$product_id&added=1"); + exit; + } catch (PDOException $e) { + $message = '
Error adding variant: ' . $e->getMessage() . '
'; + } } // Handle Edit Variant @@ -39,20 +45,37 @@ if (isset($_POST['action']) && $_POST['action'] === 'edit_variant') { $name_ar = $_POST['name_ar'] ?? ''; $price_adj = $_POST['price_adjustment']; - $stmt = $pdo->prepare("UPDATE product_variants SET name = ?, name_ar = ?, price_adjustment = ? WHERE id = ?"); - $stmt->execute([$name, $name_ar, $price_adj, $id]); - header("Location: product_variants.php?product_id=$product_id"); - exit; + try { + $stmt = $pdo->prepare("UPDATE product_variants SET name = ?, name_ar = ?, price_adjustment = ? WHERE id = ?"); + $stmt->execute([$name, $name_ar, $price_adj, $id]); + header("Location: product_variants.php?product_id=$product_id&updated=1"); + exit; + } catch (PDOException $e) { + $message = '
Error updating variant: ' . $e->getMessage() . '
'; + } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { - $pdo->prepare("DELETE FROM product_variants WHERE id = ?")->execute([$_GET['delete']]); - header("Location: product_variants.php?product_id=$product_id"); - exit; + try { + $id = (int)$_GET['delete']; + $pdo->prepare("UPDATE product_variants SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: product_variants.php?product_id=$product_id&deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error deleting variant: ' . $e->getMessage() . '
'; + } } -$query = "SELECT * FROM product_variants WHERE product_id = ? ORDER BY price_adjustment ASC"; +if (isset($_GET['added'])) { + $message = '
Variant added successfully!
'; +} elseif (isset($_GET['updated'])) { + $message = '
Variant updated successfully!
'; +} elseif (isset($_GET['deleted'])) { + $message = '
Variant removed successfully!
'; +} + +$query = "SELECT * FROM product_variants WHERE product_id = ? AND is_deleted = 0 ORDER BY price_adjustment ASC"; $variants_pagination = paginate_query($pdo, $query, [$product_id]); $variants = $variants_pagination['data']; @@ -74,6 +97,8 @@ $effective_base_price = get_product_price($product); + +
diff --git a/admin/products.php b/admin/products.php index b4a3264..83138e6 100644 --- a/admin/products.php +++ b/admin/products.php @@ -11,117 +11,109 @@ $message = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { $action = $_POST['action']; $id = isset($_POST['id']) ? (int)$_POST['id'] : null; + $name = $_POST['name']; + $name_ar = $_POST['name_ar'] ?? ''; + $category_id = (int)$_POST['category_id']; + $price = (float)$_POST['price']; + $cost_price = (float)($_POST['cost_price'] ?? 0); + $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); + $description = $_POST['description'] ?? ''; + $promo_discount_percent = !empty($_POST['promo_discount_percent']) ? (float)$_POST['promo_discount_percent'] : null; + $promo_date_from = !empty($_POST['promo_date_from']) ? $_POST['promo_date_from'] : null; + $promo_date_to = !empty($_POST['promo_date_to']) ? $_POST['promo_date_to'] : null; - if ($action === 'cancel_promotion' && $id) { - if (!has_permission('products_edit')) { - $message = '
Access Denied: You do not have permission to edit products.
'; - } else { - try { - $stmt = $pdo->prepare("UPDATE products SET promo_discount_percent = NULL, promo_date_from = NULL, promo_date_to = NULL WHERE id = ?"); - $stmt->execute([$id]); - $message = '
Promotion cancelled successfully!
'; - } catch (PDOException $e) { - $message = '
Database error: ' . $e->getMessage() . '
'; - } - } - } else { - $name = $_POST['name']; - $name_ar = $_POST['name_ar'] ?? ''; - $category_id = $_POST['category_id']; - $price = $_POST['price']; - $cost_price = $_POST['cost_price'] ?: 0; - $stock_quantity = $_POST['stock_quantity'] ?: 0; - $description = $_POST['description']; + $image_url = null; + if ($id) { + $stmt = $pdo->prepare("SELECT image_url FROM products WHERE id = ?"); + $stmt->execute([$id]); + $image_url = $stmt->fetchColumn(); + } + + if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { + $uploadDir = __DIR__ . '/../assets/images/products/'; + if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true); - $promo_discount_percent = $_POST['promo_discount_percent'] !== '' ? $_POST['promo_discount_percent'] : null; - $promo_date_from = $_POST['promo_date_from'] !== '' ? $_POST['promo_date_from'] : null; - $promo_date_to = $_POST['promo_date_to'] !== '' ? $_POST['promo_date_to'] : null; - - $image_url = null; - if ($id) { - $stmt = $pdo->prepare("SELECT image_url FROM products WHERE id = ?"); - $stmt->execute([$id]); - $image_url = $stmt->fetchColumn(); - } else { - $image_url = 'https://placehold.co/400x300?text=' . urlencode($name); - } - - if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { - $uploadDir = __DIR__ . '/../assets/images/products/'; - if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true); - - $file_ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION)); - if (in_array($file_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) { - $fileName = uniqid('prod_') . '.' . $file_ext; - if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadDir . $fileName)) { - $image_url = 'assets/images/products/' . $fileName; - } + $file_ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION)); + if (in_array($file_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) { + $fileName = uniqid('prod_') . '.' . $file_ext; + if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadDir . $fileName)) { + $image_url = 'assets/images/products/' . $fileName; } } + } - try { - if ($action === 'edit_product' && $id) { - // Check for edit OR add (for backward compatibility) - if (!has_permission('products_edit') && !has_permission('products_add')) { - $message = '
Access Denied: You do not have permission to edit products.
'; - } else { - $stmt = $pdo->prepare("UPDATE products SET name = ?, name_ar = ?, category_id = ?, price = ?, cost_price = ?, stock_quantity = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?"); - $stmt->execute([$name, $name_ar, $category_id, $price, $cost_price, $stock_quantity, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id]); - $message = '
Product updated successfully!
'; - } - } elseif ($action === 'add_product') { - if (!has_permission('products_add')) { - $message = '
Access Denied: You do not have permission to add products.
'; - } else { - $stmt = $pdo->prepare("INSERT INTO products (name, name_ar, category_id, price, cost_price, stock_quantity, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$name, $name_ar, $category_id, $price, $cost_price, $stock_quantity, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to]); - $message = '
Product created successfully!
'; - } + try { + if ($action === 'edit_product' && $id) { + if (!has_permission('products_edit')) { + $message = '
Access Denied: You do not have permission to edit products.
'; + } else { + $stmt = $pdo->prepare("UPDATE products SET name = ?, name_ar = ?, category_id = ?, price = ?, cost_price = ?, stock_quantity = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?"); + $stmt->execute([$name, $name_ar, $category_id, $price, $cost_price, $stock_quantity, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id]); + $message = '
Product updated successfully!
'; + } + } elseif ($action === 'add_product') { + if (!has_permission('products_add')) { + $message = '
Access Denied: You do not have permission to add products.
'; + } else { + $stmt = $pdo->prepare("INSERT INTO products (name, name_ar, category_id, price, cost_price, stock_quantity, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$name, $name_ar, $category_id, $price, $cost_price, $stock_quantity, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to]); + $message = '
Product created successfully!
'; } - } catch (PDOException $e) { - $message = '
Database error: ' . $e->getMessage() . '
'; } + } catch (PDOException $e) { + $message = '
Database error: ' . $e->getMessage() . '
'; } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('products_del')) { $message = '
Access Denied: You do not have permission to delete products.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM products WHERE id = ?")->execute([$id]); - header("Location: products.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Use Soft Delete to preserve data integrity for orders + $pdo->prepare("UPDATE products SET is_deleted = 1 WHERE id = ?")->execute([$id]); + $pdo->prepare("UPDATE product_variants SET is_deleted = 1 WHERE product_id = ?")->execute([$id]); + header("Location: products.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error deleting product: ' . $e->getMessage() . '
'; + } } } -$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll(); +if (isset($_GET['deleted'])) { + $message = '
Product removed successfully!
'; +} + +$categories = $pdo->query("SELECT * FROM categories WHERE is_deleted = 0 ORDER BY name ASC")->fetchAll(); + +// Build Query with Filters +$params = []; +$where = ["p.is_deleted = 0"]; // Base filter for soft delete $search = $_GET['search'] ?? ''; $category_filter = $_GET['category_filter'] ?? ''; -$params = []; -$where = []; - -$query = "SELECT p.*, c.name as category_name - FROM products p - LEFT JOIN categories c ON p.category_id = c.id"; if ($search) { - $where[] = "(p.name LIKE ? OR p.name_ar LIKE ? OR p.description LIKE ?)"; - $params[] = "%$search%"; - $params[] = "%$search%"; - $params[] = "%$search%"; + $where[] = "(p.name LIKE :search OR p.name_ar LIKE :search OR p.description LIKE :search)"; + $params[':search'] = "%$search%"; } if ($category_filter) { - $where[] = "p.category_id = ?"; - $params[] = $category_filter; + $where[] = "p.category_id = :category_id"; + $params[':category_id'] = $category_filter; } -if (!empty($where)) $query .= " WHERE " . implode(" AND ", $where); -$query .= " ORDER BY p.id DESC"; - +$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; + +$query = "SELECT p.*, c.name as category_name + FROM products p + LEFT JOIN categories c ON p.category_id = c.id + $where_clause + ORDER BY p.name ASC"; + $products_pagination = paginate_query($pdo, $query, $params); $products = $products_pagination['data']; @@ -131,7 +123,7 @@ include 'includes/header.php';

-

Manage items, stock, and pricing

+

Manage your menu items and stock

+ + + +
+
+ +
- -
- -

-

Try adjusting your filters or search terms.

-
- -
+
+
+ +
+ +
- + - - + + - = $product['promo_date_from'] && - $today <= $product['promo_date_to']; - $has_promo_data = !empty($product['promo_discount_percent']); - ?> + - + + + + + +
Promotion
-
- +
+ + + +
+ +
+
-
- -
- -
+
+
- - - - + - -
-
- -
+
+ 0): ?> + :
-
- - -% - - - - - - - - -
- - - -
- -
+ + + + + +
+ 0): ?> + + % Off + + + - +
- + - +
+ + +
+
- +
- +
@@ -279,89 +278,81 @@ include 'includes/header.php'; -
-
-
- - -
-
- -
- -
-
-
-
- - -
-
- -
- - -
-
-
-
-
- - -
-
- - -
-
+
+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
-
-
-
Promotion
-
- - -
-
- - -
-
- - -
+ + +
+ +
+ + +
+ +
+ +
+ +
+
-
-
- - -
- -
- -
- -
- - Recommended: Square image, max 2MB. +
+
Promotion Settings
+
+
+ + +
+
+ + +
+
+ + +
@@ -384,25 +375,25 @@ function prepareAddForm() { document.getElementById('productImagePreview').style.display = 'none'; } -function prepareEditForm(prod) { - if (!prod) return; - document.getElementById('productModalTitle').innerText = ' : ' + prod.name; +function prepareEditForm(p) { + if (!p) return; + document.getElementById('productModalTitle').innerText = ': ' + p.name; document.getElementById('productAction').value = 'edit_product'; - document.getElementById('productId').value = prod.id; - document.getElementById('productName').value = prod.name; - document.getElementById('productNameAr').value = prod.name_ar || ''; - document.getElementById('productCategoryId').value = prod.category_id; - document.getElementById('productPrice').value = prod.price; - document.getElementById('productCostPrice').value = prod.cost_price || ''; - document.getElementById('productStockQuantity').value = prod.stock_quantity || '0'; - document.getElementById('productDescription').value = prod.description || ''; - document.getElementById('productPromoDiscount').value = prod.promo_discount_percent || ''; - document.getElementById('productPromoFrom').value = prod.promo_date_from || ''; - document.getElementById('productPromoTo').value = prod.promo_date_to || ''; + document.getElementById('productId').value = p.id; + document.getElementById('productName').value = p.name; + document.getElementById('productNameAr').value = p.name_ar || ''; + document.getElementById('productCategoryId').value = p.category_id; + document.getElementById('productPrice').value = p.price; + document.getElementById('productCostPrice').value = p.cost_price || ''; + document.getElementById('productStockQuantity').value = p.stock_quantity || '0'; + document.getElementById('productDescription').value = p.description || ''; + document.getElementById('productPromoDiscount').value = p.promo_discount_percent || ''; + document.getElementById('productPromoFrom').value = p.promo_date_from || ''; + document.getElementById('productPromoTo').value = p.promo_date_to || ''; - if (prod.image_url) { + if (p.image_url) { const preview = document.getElementById('productImagePreview'); - preview.src = prod.image_url.startsWith('http') ? prod.image_url : '../' + prod.image_url; + preview.src = p.image_url.startsWith('http') ? p.image_url : '../' + p.image_url; preview.style.display = 'block'; } else { document.getElementById('productImagePreview').style.display = 'none'; @@ -448,4 +439,4 @@ async function translateTo(targetLang) { - \ No newline at end of file + diff --git a/admin/purchases.php b/admin/purchases.php index 4b07aa6..5e861ee 100644 --- a/admin/purchases.php +++ b/admin/purchases.php @@ -81,14 +81,22 @@ if (isset($_GET['delete'])) { if (!has_permission('purchases_del')) { $message = '
Access Denied: You do not have permission to delete purchases.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM purchases WHERE id = ?")->execute([$id]); - header("Location: purchases.php?msg=deleted"); - exit; + try { + $id = $_GET['delete']; + $pdo->beginTransaction(); + $pdo->prepare("DELETE FROM purchase_items WHERE purchase_id = ?")->execute([$id]); + $pdo->prepare("DELETE FROM purchases WHERE id = ?")->execute([$id]); + $pdo->commit(); + header("Location: purchases.php?deleted=1"); + exit; + } catch (PDOException $e) { + $pdo->rollBack(); + $message = '
Error deleting purchase: ' . $e->getMessage() . '
'; + } } } -if (isset($_GET['msg']) && $_GET['msg'] === 'deleted') { +if (isset($_GET['deleted'])) { $message = ''; } @@ -537,4 +545,4 @@ document.addEventListener('DOMContentLoaded', function() { } - \ No newline at end of file + diff --git a/admin/ratings.php b/admin/ratings.php index 09c5505..f25b761 100644 --- a/admin/ratings.php +++ b/admin/ratings.php @@ -27,13 +27,21 @@ if (isset($_GET['delete'])) { if (!has_permission('settings')) { // Use settings permission for deletion $message = '
Access Denied.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM staff_ratings WHERE id = ?")->execute([$id]); - header("Location: ratings.php"); - exit; + try { + $id = $_GET['delete']; + $pdo->prepare("DELETE FROM staff_ratings WHERE id = ?")->execute([$id]); + header("Location: ratings.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error deleting rating: ' . $e->getMessage() . '
'; + } } } +if (isset($_GET['deleted'])) { + $message = '
Rating deleted successfully!
'; +} + $staff = $pdo->query("SELECT id, full_name, username FROM users WHERE is_ratable = 1 ORDER BY full_name ASC")->fetchAll(); $query = "SELECT r.*, u.full_name as staff_name, u.username as staff_username @@ -178,4 +186,4 @@ function openAddModal() { } - + \ No newline at end of file diff --git a/admin/suppliers.php b/admin/suppliers.php index 0a5e776..b26779b 100644 --- a/admin/suppliers.php +++ b/admin/suppliers.php @@ -43,19 +43,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('suppliers_del')) { $message = '
Access Denied: You do not have permission to delete suppliers.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM suppliers WHERE id = ?")->execute([$id]); - header("Location: suppliers.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Soft delete to preserve data integrity for purchases + $pdo->prepare("UPDATE suppliers SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: suppliers.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing supplier: ' . $e->getMessage() . '
'; + } } } -$query = "SELECT * FROM suppliers ORDER BY name ASC"; +if (isset($_GET['deleted'])) { + $message = '
Supplier removed successfully!
'; +} + +$query = "SELECT * FROM suppliers WHERE is_deleted = 0 ORDER BY name ASC"; $suppliers_pagination = paginate_query($pdo, $query); $suppliers = $suppliers_pagination['data']; @@ -109,7 +118,7 @@ include 'includes/header.php'; - + @@ -209,4 +218,4 @@ function openEditModal(supplier) { - \ No newline at end of file + diff --git a/admin/tables.php b/admin/tables.php index c0d8083..63455eb 100644 --- a/admin/tables.php +++ b/admin/tables.php @@ -42,23 +42,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('tables_del')) { $message = '
Access Denied: You do not have permission to delete tables.
'; } else { - $id = $_GET['delete']; - $pdo->prepare("DELETE FROM tables WHERE id = ?")->execute([$id]); - header("Location: tables.php"); - exit; + try { + $id = (int)$_GET['delete']; + // Soft delete to avoid breaking historical order integrity + $pdo->prepare("UPDATE tables SET is_deleted = 1 WHERE id = ?")->execute([$id]); + header("Location: tables.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing table: ' . $e->getMessage() . '
'; + } } } -$areas = $pdo->query("SELECT * FROM areas ORDER BY name ASC")->fetchAll(); +if (isset($_GET['deleted'])) { + $message = '
Table removed successfully!
'; +} + +$areas = $pdo->query("SELECT * FROM areas WHERE is_deleted = 0 ORDER BY name ASC")->fetchAll(); $query = "SELECT t.*, a.name as area_name FROM tables t LEFT JOIN areas a ON t.area_id = a.id + WHERE t.is_deleted = 0 ORDER BY a.name ASC, t.table_number ASC"; $tables_pagination = paginate_query($pdo, $query); $tables = $tables_pagination['data']; @@ -119,7 +129,7 @@ include 'includes/header.php'; - + diff --git a/admin/user_groups.php b/admin/user_groups.php index b985a69..0752dfa 100644 --- a/admin/user_groups.php +++ b/admin/user_groups.php @@ -33,22 +33,34 @@ if (isset($_GET['delete'])) { if (!has_permission('user_groups_del')) { $message = '
Access Denied: You do not have permission to delete user groups.
'; } else { - $id = $_GET['delete']; - // Don't allow deleting Administrator group - $stmt = $pdo->prepare("SELECT name FROM user_groups WHERE id = ?"); - $stmt->execute([$id]); - $groupName = $stmt->fetchColumn(); - - if ($groupName === 'Administrator') { - $message = '
The Administrator group cannot be deleted.
'; - } else { - $pdo->prepare("DELETE FROM user_groups WHERE id = ?")->execute([$id]); - header("Location: user_groups.php"); - exit; + try { + $id = $_GET['delete']; + // Don't allow deleting Administrator group + $stmt = $pdo->prepare("SELECT name FROM user_groups WHERE id = ?"); + $stmt->execute([$id]); + $groupName = $stmt->fetchColumn(); + + if ($groupName === 'Administrator') { + $message = '
The Administrator group cannot be deleted.
'; + } else { + $pdo->prepare("DELETE FROM user_groups WHERE id = ?")->execute([$id]); + header("Location: user_groups.php?deleted=1"); + exit; + } + } catch (PDOException $e) { + if ($e->getCode() == '23000') { + $message = '
Cannot delete this group because it is linked to users.
'; + } else { + $message = '
Error deleting group: ' . $e->getMessage() . '
'; + } } } } +if (isset($_GET['deleted'])) { + $message = '
User group deleted successfully!
'; +} + $availablePermissions = [ 'dashboard' => 'Dashboard', 'pos' => 'POS Terminal', @@ -203,4 +215,4 @@ include 'includes/header.php';
- \ No newline at end of file + diff --git a/admin/users.php b/admin/users.php index 7d030b3..28966d6 100644 --- a/admin/users.php +++ b/admin/users.php @@ -104,29 +104,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } } -// Handle Delete +// Handle Delete (Soft Delete) if (isset($_GET['delete'])) { if (!has_permission('users_del')) { $message = '
Access Denied: You do not have permission to delete users.
'; } else { - $id = $_GET['delete']; + $id = (int)$_GET['delete']; // Don't allow deleting current user if ($id == $_SESSION['user']['id']) { - $message = '
You cannot delete your own account.
'; + $message = '
You cannot remove your own account.
'; } else { - $pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]); - header("Location: users.php"); - exit; + try { + // Use Soft Delete to preserve data integrity for orders and staff ratings + $pdo->prepare("UPDATE users SET is_deleted = 1, is_active = 0 WHERE id = ?")->execute([$id]); + header("Location: users.php?deleted=1"); + exit; + } catch (PDOException $e) { + $message = '
Error removing user: ' . $e->getMessage() . '
'; + } } } } -$groups = $pdo->query("SELECT * FROM user_groups ORDER BY name ASC")->fetchAll(); -$all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name ASC")->fetchAll(); +if (isset($_GET['deleted'])) { + $message = '
User removed successfully!
'; +} + +$groups = $pdo->query("SELECT * FROM user_groups WHERE is_deleted = 0 ORDER BY name ASC")->fetchAll(); +$all_outlets = $pdo->query("SELECT * FROM outlets WHERE is_deleted = 0 ORDER BY name ASC")->fetchAll(); $query = "SELECT u.*, g.name as group_name FROM users u LEFT JOIN user_groups g ON u.group_id = g.id + WHERE u.is_deleted = 0 ORDER BY u.id DESC"; $users_pagination = paginate_query($pdo, $query); $users = $users_pagination['data']; @@ -212,7 +222,7 @@ include 'includes/header.php'; - + diff --git a/api/kitchen.php b/api/kitchen.php index 9c1f795..fca76e8 100644 --- a/api/kitchen.php +++ b/api/kitchen.php @@ -15,10 +15,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $stmt = $pdo->prepare(" SELECT o.id, o.table_number, o.order_type, o.status, o.created_at, o.customer_name, - oi.quantity, p.name as product_name, v.name as variant_name + oi.quantity, COALESCE(p.name, oi.product_name) as product_name, COALESCE(v.name, oi.variant_name) as variant_name FROM orders o JOIN order_items oi ON o.id = oi.order_id - JOIN products p ON oi.product_id = p.id + LEFT JOIN products p ON oi.product_id = p.id LEFT JOIN product_variants v ON oi.variant_id = v.id WHERE o.status IN ('pending', 'preparing', 'ready') AND o.outlet_id = :outlet_id diff --git a/api/order.php b/api/order.php index f92ef9a..6a70db6 100644 --- a/api/order.php +++ b/api/order.php @@ -164,13 +164,15 @@ try { $unit_price = get_product_price($product); + $variant_name = null; // Add variant adjustment if ($vid) { - $vStmt = $pdo->prepare("SELECT price_adjustment FROM product_variants WHERE id = ? AND product_id = ?"); + $vStmt = $pdo->prepare("SELECT name, price_adjustment FROM product_variants WHERE id = ? AND product_id = ?"); $vStmt->execute([$vid, $pid]); - $vAdjustment = $vStmt->fetchColumn(); - if ($vAdjustment !== false) { - $unit_price += floatval($vAdjustment); + $variant = $vStmt->fetch(PDO::FETCH_ASSOC); + if ($variant) { + $unit_price += floatval($variant['price_adjustment']); + $variant_name = $variant['name']; } } @@ -179,10 +181,11 @@ try { $processed_items[] = [ 'product_id' => $pid, + 'product_name' => $product['name'], 'variant_id' => $vid, + 'variant_name' => $variant_name, 'quantity' => $qty, - 'unit_price' => $unit_price, - 'name' => $product['name'] + 'unit_price' => $unit_price ]; } } @@ -251,23 +254,20 @@ try { } // Insert Items and Update Stock - $item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, variant_id, quantity, unit_price) VALUES (?, ?, ?, ?, ?)"); + $item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, product_name, variant_id, variant_name, quantity, unit_price) VALUES (?, ?, ?, ?, ?, ?, ?)"); $stock_stmt = $pdo->prepare("UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?"); - $varNameStmt = $pdo->prepare("SELECT name FROM product_variants WHERE id = ?"); $order_items_list = []; foreach ($processed_items as $pi) { - $item_stmt->execute([$order_id, $pi['product_id'], $pi['variant_id'], $pi['quantity'], $pi['unit_price']]); + $item_stmt->execute([$order_id, $pi['product_id'], $pi['product_name'], $pi['variant_id'], $pi['variant_name'], $pi['quantity'], $pi['unit_price']]); // Decrement Stock $stock_stmt->execute([$pi['quantity'], $pi['product_id']]); - $pName = $pi['name']; - if ($pi['variant_id']) { - $varNameStmt->execute([$pi['variant_id']]); - $vName = $varNameStmt->fetchColumn(); - if ($vName) $pName .= " ($vName)"; + $pName = $pi['product_name']; + if ($pi['variant_name']) { + $pName .= " ({$pi['variant_name']})"; } $order_items_list[] = "{$pi['quantity']} x $pName"; } @@ -335,4 +335,4 @@ You've earned *{points_earned} points* with this order. if ($pdo->inTransaction()) $pdo->rollBack(); error_log("Order Error: " . $e->getMessage()); echo json_encode(['success' => false, 'error' => $e->getMessage()]); -} \ No newline at end of file +} diff --git a/db/migrations/035_soft_delete_system.sql b/db/migrations/035_soft_delete_system.sql new file mode 100644 index 0000000..10a2735 --- /dev/null +++ b/db/migrations/035_soft_delete_system.sql @@ -0,0 +1,11 @@ +-- Soft Delete System +ALTER TABLE categories ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE products ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE outlets ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE tables ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE areas ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE users ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE user_groups ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE suppliers ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE expense_categories ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; +ALTER TABLE payment_types ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; diff --git a/db/migrations/036_soft_delete_variants.sql b/db/migrations/036_soft_delete_variants.sql new file mode 100644 index 0000000..eb46852 --- /dev/null +++ b/db/migrations/036_soft_delete_variants.sql @@ -0,0 +1,2 @@ +-- Soft delete for product variants +ALTER TABLE product_variants ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; diff --git a/db/migrations/037_order_items_preservation.sql b/db/migrations/037_order_items_preservation.sql new file mode 100644 index 0000000..33a2822 --- /dev/null +++ b/db/migrations/037_order_items_preservation.sql @@ -0,0 +1,11 @@ +-- Preserve names in order_items for historical data +ALTER TABLE order_items ADD COLUMN product_name VARCHAR(255) AFTER product_id; +ALTER TABLE order_items ADD COLUMN variant_name VARCHAR(255) AFTER variant_id; + +-- Populate existing names +UPDATE order_items oi JOIN products p ON oi.product_id = p.id SET oi.product_name = p.name; +UPDATE order_items oi JOIN product_variants pv ON oi.variant_id = pv.id SET oi.variant_name = pv.name; + +-- Modify foreign key to allow hard delete while preserving historical info +ALTER TABLE order_items DROP FOREIGN KEY order_items_ibfk_2; +ALTER TABLE order_items ADD CONSTRAINT order_items_ibfk_2 FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE SET NULL; diff --git a/pos.php b/pos.php index 96fefa8..5d18ffa 100644 --- a/pos.php +++ b/pos.php @@ -18,12 +18,12 @@ $currentUser = get_logged_user(); // Fetch outlets based on user assignment if (has_permission('all')) { - $outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll(); + $outlets = $pdo->query("SELECT * FROM outlets WHERE is_deleted = 0 ORDER BY name")->fetchAll(); } else { $stmt = $pdo->prepare(" SELECT o.* FROM outlets o JOIN user_outlets uo ON o.id = uo.outlet_id - WHERE uo.user_id = ? + WHERE uo.user_id = ? AND o.is_deleted = 0 ORDER BY o.name "); $stmt->execute([$currentUser['id']]); @@ -46,12 +46,12 @@ if (!has_permission('all')) { } } -$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll(); -$all_products = $pdo->query("SELECT p.*, c.name as category_name, c.name_ar as category_name_ar FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll(); -$payment_types = $pdo->query("SELECT * FROM payment_types WHERE is_active = 1 ORDER BY id")->fetchAll(); +$categories = $pdo->query("SELECT * FROM categories WHERE is_deleted = 0 ORDER BY sort_order")->fetchAll(); +$all_products = $pdo->query("SELECT p.*, c.name as category_name, c.name_ar as category_name_ar FROM products p JOIN categories c ON p.category_id = c.id WHERE p.is_deleted = 0 AND c.is_deleted = 0")->fetchAll(); +$payment_types = $pdo->query("SELECT * FROM payment_types WHERE is_active = 1 AND is_deleted = 0 ORDER BY id")->fetchAll(); // Fetch variants -$variants_raw = $pdo->query("SELECT * FROM product_variants ORDER BY price_adjustment ASC")->fetchAll(); +$variants_raw = $pdo->query("SELECT * FROM product_variants WHERE is_deleted = 0 ORDER BY price_adjustment ASC")->fetchAll(); $variants_by_product = []; foreach ($variants_raw as $v) { $variants_by_product[$v['product_id']][] = $v; @@ -478,4 +478,4 @@ if (!$loyalty_settings) { - \ No newline at end of file + diff --git a/rate.php b/rate.php index 571d237..c8db233 100644 --- a/rate.php +++ b/rate.php @@ -51,6 +51,9 @@ $users = $stmt->fetchAll(PDO::FETCH_ASSOC); + + +