rate editing

This commit is contained in:
Flatlogic Bot 2026-02-25 18:22:13 +00:00
parent c850a45169
commit 9eaaf40d0f
24 changed files with 576 additions and 403 deletions

View File

@ -76,20 +76,28 @@ if (isset($_GET['delete'])) {
if (!has_permission('ads')) {
$message = '<div class="alert alert-danger">Access Denied.</div>';
} 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 = '<div class="alert alert-danger">Error deleting advertisement: ' . $e->getMessage() . '</div>';
}
header("Location: ads.php");
exit;
}
}
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Advertisement deleted successfully!</div>';
}
$query = "SELECT * FROM ads_images ORDER BY sort_order ASC, created_at DESC";
$promos_pagination = paginate_query($pdo, $query);
$promos = $promos_pagination['data'];

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete areas.</div>';
} 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 = '<div class="alert alert-danger">Error removing area: ' . $e->getMessage() . '</div>';
}
}
}
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name ASC")->fetchAll();
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Area removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('areas_del')): ?>
<a href="?delete=<?= $area['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this area and all its tables?')"><i class="bi bi-trash"></i></a>
<a href="?delete=<?= $area['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('<?= t('are_you_sure') ?>')"><i class="bi bi-trash"></i></a>
<?php endif; ?>
</td>
</tr>

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete categories.</div>';
} 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 = '<div class="alert alert-danger">Error removing category: ' . $e->getMessage() . '</div>';
}
}
}
$query = "SELECT * FROM categories ORDER BY name ASC";
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Category removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('categories_del')): ?>
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Delete category? Ensure no products are linked.')"><?= t('delete') ?></a>
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('<?= t('are_you_sure') ?>')"><?= t('delete') ?></a>
<?php endif; ?>
</div>
</td>

View File

@ -48,13 +48,25 @@ if (isset($_GET['delete'])) {
if (!has_permission('customers_del')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete customers.</div>';
} 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 = '<div class="alert alert-danger">Cannot delete this customer because they are linked to other records (e.g., orders).</div>';
} else {
$message = '<div class="alert alert-danger">Error deleting customer: ' . $e->getMessage() . '</div>';
}
}
}
}
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Customer deleted successfully!</div>';
}
$search = $_GET['search'] ?? '';
$params = [];
$query = "SELECT * FROM customers";
@ -202,7 +214,7 @@ include 'includes/header.php';
</div>
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-modal="modal" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm">Save Customer Profile</button>
</div>
</form>

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete expense categories.</div>';
} 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 = '<div class="alert alert-danger">Error removing category: ' . $e->getMessage() . '</div>';
}
}
}
$query = "SELECT * FROM expense_categories ORDER BY name ASC";
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Expense category removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('expense_categories_del')): ?>
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this expense category?')"><i class="bi bi-trash"></i></a>
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('<?= t('are_you_sure') ?>')"><i class="bi bi-trash"></i></a>
<?php endif; ?>
</td>
</tr>

View File

@ -45,14 +45,18 @@ if (isset($_GET['delete'])) {
if (!has_permission('expenses_del')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete expenses.</div>';
} 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 = '<div class="alert alert-danger">Error deleting expense: ' . $e->getMessage() . '</div>';
}
}
}
if (isset($_GET['success']) && $_GET['success'] === 'deleted') {
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Expense deleted successfully!</div>';
}

View File

@ -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]);

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete outlets.</div>';
} 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 = '<div class="alert alert-danger">Error removing outlet: ' . $e->getMessage() . '</div>';
}
}
}
$query = "SELECT * FROM outlets ORDER BY id DESC";
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Outlet removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('outlets_del')): ?>
<a href="?delete=<?= $outlet['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this outlet?')"><i class="bi bi-trash"></i></a>
<a href="?delete=<?= $outlet['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('<?= t('are_you_sure') ?>')"><i class="bi bi-trash"></i></a>
<?php endif; ?>
</td>
</tr>

View File

@ -50,13 +50,25 @@ if (isset($_GET['delete'])) {
if (!has_permission('payment_types_del')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete payment types.</div>';
} 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 = '<div class="alert alert-danger">Cannot delete this payment type because it is linked to other records (e.g., orders).</div>';
} else {
$message = '<div class="alert alert-danger">Error deleting payment type: ' . $e->getMessage() . '</div>';
}
}
}
}
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Payment type deleted successfully!</div>';
}
$query = "SELECT * FROM payment_types ORDER BY id ASC";
$payments_pagination = paginate_query($pdo, $query);
$payment_types = $payments_pagination['data'];

View File

@ -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 = '<div class="alert alert-danger">Error adding variant: ' . $e->getMessage() . '</div>';
}
}
// 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 = '<div class="alert alert-danger">Error updating variant: ' . $e->getMessage() . '</div>';
}
}
// 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 = '<div class="alert alert-danger">Error deleting variant: ' . $e->getMessage() . '</div>';
}
}
$query = "SELECT * FROM product_variants WHERE product_id = ? ORDER BY price_adjustment ASC";
if (isset($_GET['added'])) {
$message = '<div class="alert alert-success">Variant added successfully!</div>';
} elseif (isset($_GET['updated'])) {
$message = '<div class="alert alert-success">Variant updated successfully!</div>';
} elseif (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Variant removed successfully!</div>';
}
$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);
</div>
</div>
<?= $message ?>
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<!-- Pagination Controls -->

View File

@ -11,116 +11,108 @@ $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 = '<div class="alert alert-danger">Access Denied: You do not have permission to edit products.</div>';
} 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 = '<div class="alert alert-success">Promotion cancelled successfully!</div>';
} catch (PDOException $e) {
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
$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);
$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;
}
}
} 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'];
}
$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;
}
try {
if ($action === 'edit_product' && $id) {
if (!has_permission('products_edit')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to edit products.</div>';
} 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 = '<div class="alert alert-success">Product updated successfully!</div>';
}
} elseif ($action === 'add_product') {
if (!has_permission('products_add')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add products.</div>';
} 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 = '<div class="alert alert-success">Product created successfully!</div>';
}
}
try {
if ($action === 'edit_product' && $id) {
// Check for edit OR add (for backward compatibility)
if (!has_permission('products_edit') && !has_permission('products_add')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to edit products.</div>';
} 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 = '<div class="alert alert-success">Product updated successfully!</div>';
}
} elseif ($action === 'add_product') {
if (!has_permission('products_add')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add products.</div>';
} 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 = '<div class="alert alert-success">Product created successfully!</div>';
}
}
} catch (PDOException $e) {
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
}
} catch (PDOException $e) {
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
}
}
// Handle Delete
// Handle Delete (Soft Delete)
if (isset($_GET['delete'])) {
if (!has_permission('products_del')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete products.</div>';
} 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 = '<div class="alert alert-danger">Error deleting product: ' . $e->getMessage() . '</div>';
}
}
}
$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll();
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Product removed successfully!</div>';
}
$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';
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1"><?= t('products') ?></h2>
<p class="text-muted mb-0">Manage items, stock, and pricing</p>
<p class="text-muted mb-0">Manage your menu items and stock</p>
</div>
<?php if (has_permission('products_add')): ?>
<button class="btn btn-primary btn-lg shadow-sm" data-bs-toggle="modal" data-bs-target="#productModal" onclick="prepareAddForm()" style="border-radius: 12px;">
@ -142,128 +134,135 @@ include 'includes/header.php';
<?= $message ?>
<div class="filter-bar mb-4 p-3 bg-white rounded-4 shadow-sm">
<form method="GET" class="row g-3 align-items-center">
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-light border-0 text-muted"><i class="bi bi-search"></i></span>
<input type="text" name="search" class="form-control border-0 bg-light" placeholder="<?= t('search') ?>..." value="<?= htmlspecialchars($search) ?>" style="border-radius: 0 10px 10px 0;">
<!-- Filters -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body bg-light">
<form method="GET" class="row g-3 align-items-end">
<div class="col-md-5">
<label class="form-label small fw-bold text-muted"><?= t('search') ?></label>
<div class="input-group">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
<input type="text" name="search" class="form-control border-start-0" placeholder="Search by name or description..." value="<?= htmlspecialchars($search) ?>">
</div>
</div>
</div>
<div class="col-md-3">
<select name="category_filter" class="form-select border-0 bg-light rounded-3" onchange="this.form.submit()">
<option value=""><?= t('all') ?> <?= t('categories') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= $category_filter == $cat['id'] ? 'selected' : '' ?>><?= htmlspecialchars($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</form>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted"><?= t('category') ?></label>
<select name="category_filter" class="form-select">
<option value=""><?= t('all') ?> <?= t('categories') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= $category_filter == $cat['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-filter"></i> <?= t('filter') ?>
</button>
<a href="products.php" class="btn btn-outline-secondary" title="Clear Filters">
<i class="bi bi-arrow-counterclockwise"></i>
</a>
</div>
</div>
</form>
</div>
</div>
<?php if (empty($products)): ?>
<div class="text-center py-5 bg-white rounded-4 shadow-sm">
<i class="bi bi-box-seam display-1 text-muted opacity-25 mb-3 d-block"></i>
<h4 class="text-dark"><?= t('none') ?></h4>
<p class="text-muted">Try adjusting your filters or search terms.</p>
</div>
<?php else: ?>
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card-body p-0">
<!-- Pagination Controls -->
<div class="p-3 border-bottom bg-light">
<?php render_pagination_controls($products_pagination); ?>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4"><?= t('products') ?></th>
<th class="ps-4"><?= t('product') ?></th>
<th><?= t('category') ?></th>
<th><?= t('stock') ?></th>
<th><?= t('price') ?></th>
<th>Promotion</th>
<th><?= t('stock') ?></th>
<th><?= t('promotion') ?></th>
<th class="text-end pe-4"><?= t('actions') ?></th>
</tr>
</thead>
<tbody>
<?php
$today = date('Y-m-d');
foreach ($products as $product):
$is_promo_active = !empty($product['promo_discount_percent']) &&
!empty($product['promo_date_from']) &&
!empty($product['promo_date_to']) &&
$today >= $product['promo_date_from'] &&
$today <= $product['promo_date_to'];
$has_promo_data = !empty($product['promo_discount_percent']);
?>
<?php foreach ($products as $product): ?>
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" alt="" class="rounded-3 me-3 border shadow-sm" style="width: 48px; height: 48px; object-fit: cover;">
<div class="d-flex align-items-center py-2">
<?php if ($product['image_url']): ?>
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" alt="" class="rounded-3 me-3 border shadow-sm" style="width: 50px; height: 50px; object-fit: cover;">
<?php else: ?>
<div class="bg-light rounded-3 d-flex align-items-center justify-content-center me-3 border" style="width: 50px; height: 50px;">
<i class="bi bi-image text-muted opacity-50"></i>
</div>
<?php endif; ?>
<div>
<div class="fw-bold text-dark"><?= htmlspecialchars($product['name']) ?></div>
<?php if (!empty($product['name_ar'])): ?>
<div class="text-primary small fw-semibold" dir="rtl"><?= htmlspecialchars($product['name_ar']) ?></div>
<?php endif; ?>
<div class="text-muted small text-truncate" style="max-width: 180px;"><?= htmlspecialchars($product['description'] ?? '') ?></div>
<div class="fw-bold text-dark fs-6"><?= htmlspecialchars($product['name']) ?></div>
<small class="text-muted"><?= htmlspecialchars($product['name_ar'] ?? '') ?></small>
</div>
</div>
</td>
<td><span class="badge bg-light text-dark border px-2 py-1"><?= htmlspecialchars($product['category_name'] ?? t('none')) ?></span></td>
<td>
<?php $stock = (int)$product['stock_quantity']; ?>
<span class="badge <?= $stock <= 5 ? 'bg-danger-subtle text-danger' : ($stock <= 20 ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success') ?> px-2 py-1 border border-<?= $stock <= 5 ? 'danger' : ($stock <= 20 ? 'warning' : 'success') ?>">
<?= $stock ?> <?= t('stock') ?>
</span>
<span class="badge bg-light text-dark border"><?= htmlspecialchars($product['category_name'] ?? t('none')) ?></span>
</td>
<td>
<?php if ($is_promo_active): ?>
<div class="fw-bold text-primary"><?= format_currency($product['price'] * (1 - ($product['promo_discount_percent'] / 100))) ?></div>
<div class="text-muted small text-decoration-line-through"><?= format_currency($product['price']) ?></div>
<?php else: ?>
<div class="fw-bold text-dark"><?= format_currency($product['price']) ?></div>
<div class="fw-bold text-dark"><?= format_currency($product['price']) ?></div>
<?php if ($product['cost_price'] > 0): ?>
<small class="text-muted"><?= t('cost') ?>: <?= format_currency($product['cost_price']) ?></small>
<?php endif; ?>
</td>
<td>
<div class="d-flex align-items-center gap-2">
<?php if ($is_promo_active): ?>
<span class="badge bg-success rounded-pill px-2">-<?= floatval($product['promo_discount_percent']) ?>%</span>
<?php elseif (!empty($product['promo_discount_percent'])): ?>
<span class="badge bg-secondary rounded-pill px-2"><?= t('inactive') ?></span>
<?php else: ?>
<span class="text-muted small">-</span>
<?php endif; ?>
<?php if ($has_promo_data && has_permission('products_edit')): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Cancel this promotion?')">
<input type="hidden" name="action" value="cancel_promotion">
<input type="hidden" name="id" value="<?= $product['id'] ?>">
<button type="submit" class="btn btn-link p-0 text-danger" title="Cancel Promotion">
<i class="bi bi-x-circle-fill"></i>
</button>
</form>
<?php endif; ?>
</div>
<?php if ($product['stock_quantity'] <= 5): ?>
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger rounded-pill px-3"><?= $product['stock_quantity'] ?></span>
<?php else: ?>
<span class="badge bg-success bg-opacity-10 text-success border border-success rounded-pill px-3"><?= $product['stock_quantity'] ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($product['promo_discount_percent'] > 0): ?>
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning rounded-pill px-2">
<i class="bi bi-megaphone-fill me-1"></i><?= $product['promo_discount_percent'] ?>% Off
</span>
<?php else: ?>
<span class="text-muted small">-</span>
<?php endif; ?>
</td>
<td class="text-end pe-4">
<div class="d-inline-flex gap-2">
<?php if (has_permission('products_edit') || has_permission('products_add')): ?>
<?php if (has_permission('products_edit')): ?>
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3"
data-bs-toggle="modal" data-bs-target="#productModal"
onclick='prepareEditForm(<?= htmlspecialchars(json_encode($product), ENT_QUOTES, "UTF-8") ?>)'><?= t('edit') ?></button>
<?php endif; ?>
<?php if (has_permission('products_del')): ?>
<a href="?delete=<?= $product['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Delete this product? This action cannot be undone.')"><?= t('delete') ?></a>
<a href="?delete=<?= $product['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('<?= t('are_you_sure') ?>')"><?= t('delete') ?></a>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($products)): ?>
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2 opacity-25"></i>
<?= t('none') ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Bottom Pagination -->
<div class="p-3 border-top bg-light">
<?php render_pagination_controls($products_pagination); ?>
<?php render_pagination_controls($products_pagination); ?>
</div>
</div>
<?php endif; ?>
</div>
<!-- Product Modal -->
<?php if (has_permission('products_add') || has_permission('products_edit')): ?>
@ -279,89 +278,81 @@ include 'includes/header.php';
<input type="hidden" name="action" id="productAction" value="add_product">
<input type="hidden" name="id" id="productId">
<div class="row g-3 mb-4">
<div class="col-md-8">
<div class="mb-3">
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('name') ?> (EN) <span class="text-danger">*</span></span>
<a href="javascript:void(0)" onclick="translateTo('English')" class="text-decoration-none small text-primary fw-bold" id="translateBtnEn">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="name" id="productName" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('arabic_name') ?></span>
<a href="javascript:void(0)" onclick="translateTo('Arabic')" class="text-decoration-none small text-primary fw-bold" id="translateBtnAr">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<div class="input-group">
<input type="text" name="name_ar" id="productNameAr" class="form-control rounded-3 text-end" dir="rtl" placeholder="الاسم بالعربية">
</div>
</div>
<div class="row g-3">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted"><?= t('category') ?> <span class="text-danger">*</span></label>
<select name="category_id" id="productCategoryId" class="form-select rounded-3" required>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted"><?= t('price') ?> <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><?= get_company_settings()['currency_symbol'] ?></span>
<input type="number" step="0.01" name="price" id="productPrice" class="form-control rounded-3 border-0 bg-light" required>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted">COST PRICE</label>
<input type="number" step="0.01" name="cost_price" id="productCostPrice" class="form-control rounded-3 border-0 bg-light">
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted"><?= t('stock') ?></label>
<input type="number" name="stock_quantity" id="productStockQuantity" class="form-control rounded-3 border-0 bg-light">
</div>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('name') ?> (EN) <span class="text-danger">*</span></span>
<a href="javascript:void(0)" onclick="translateTo('English')" class="text-decoration-none small text-primary fw-bold" id="translateBtnEn">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="name" id="productName" class="form-control rounded-3 border-0 bg-light" required>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('arabic_name') ?></span>
<a href="javascript:void(0)" onclick="translateTo('Arabic')" class="text-decoration-none small text-primary fw-bold" id="translateBtnAr">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="name_ar" id="productNameAr" class="form-control rounded-3 border-0 bg-light" dir="rtl">
</div>
<div class="col-md-6">
<label class="form-label small fw-bold text-muted"><?= t('category') ?> <span class="text-danger">*</span></label>
<select name="category_id" id="productCategoryId" class="form-select rounded-3 border-0 bg-light" required>
<option value=""><?= t('select') ?> <?= t('category') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small fw-bold text-muted"><?= t('price') ?> <span class="text-danger">*</span></label>
<input type="number" step="0.01" name="price" id="productPrice" class="form-control rounded-3 border-0 bg-light" required>
</div>
<div class="col-md-3">
<label class="form-label small fw-bold text-muted"><?= t('cost') ?> <?= t('price') ?></label>
<input type="number" step="0.01" name="cost_price" id="productCostPrice" class="form-control rounded-3 border-0 bg-light">
</div>
<div class="col-md-4">
<div class="card bg-light border-0 h-100 rounded-4">
<div class="card-body">
<h6 class="fw-bold mb-3"><i class="bi bi-percent me-2 text-primary"></i>Promotion</h6>
<div class="mb-3">
<label class="form-label small fw-bold text-muted">DISCOUNT (%)</label>
<input type="number" step="0.01" name="promo_discount_percent" id="productPromoDiscount" class="form-control border-0 rounded-3">
</div>
<div class="mb-2">
<label class="form-label small fw-bold text-muted">START DATE</label>
<input type="date" name="promo_date_from" id="productPromoFrom" class="form-control border-0 rounded-3">
</div>
<div class="mb-0">
<label class="form-label small fw-bold text-muted">END DATE</label>
<input type="date" name="promo_date_to" id="productPromoTo" class="form-control border-0 rounded-3">
</div>
<label class="form-label small fw-bold text-muted"><?= t('stock') ?> <?= t('quantity') ?></label>
<input type="number" name="stock_quantity" id="productStockQuantity" class="form-control rounded-3 border-0 bg-light">
</div>
<div class="col-md-8">
<label class="form-label small fw-bold text-muted"><?= t('description') ?></label>
<input type="text" name="description" id="productDescription" class="form-control rounded-3 border-0 bg-light">
</div>
<div class="col-12">
<label class="form-label small fw-bold text-muted"><?= t('image') ?></label>
<div class="d-flex align-items-center gap-3 bg-light p-3 rounded-4 border border-dashed">
<img src="" id="productImagePreview" class="rounded-3 border shadow-sm" style="width: 60px; height: 60px; object-fit: cover; display: none;">
<div class="flex-grow-1">
<input type="file" name="image" class="form-control border-0 bg-transparent" accept="image/*">
</div>
</div>
</div>
</div>
<div class="mb-4">
<label class="form-label small fw-bold text-muted"><?= t('description') ?></label>
<textarea name="description" id="productDescription" class="form-control rounded-3" rows="3" placeholder="Describe your product..."></textarea>
</div>
<div class="mb-0">
<label class="form-label small fw-bold text-muted">PRODUCT IMAGE</label>
<div class="d-flex align-items-center gap-3 bg-light p-3 rounded-4 border border-dashed">
<img src="" id="productImagePreview" class="rounded-3 border shadow-sm" style="width: 64px; height: 64px; object-fit: cover; display: none;">
<div class="flex-grow-1">
<input type="file" name="image" class="form-control border-0 bg-transparent" accept="image/*">
<small class="text-muted">Recommended: Square image, max 2MB.</small>
<div class="col-12 mt-4">
<h6 class="fw-bold border-bottom pb-2 mb-3"><i class="bi bi-percent me-1"></i> Promotion Settings</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">Discount (%)</label>
<input type="number" step="0.1" name="promo_discount_percent" id="productPromoDiscount" class="form-control rounded-3 border-0 bg-light" min="0" max="100">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">From Date</label>
<input type="date" name="promo_date_from" id="productPromoFrom" class="form-control rounded-3 border-0 bg-light">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">To Date</label>
<input type="date" name="promo_date_to" id="productPromoTo" class="form-control rounded-3 border-0 bg-light">
</div>
</div>
</div>
</div>
@ -384,25 +375,25 @@ function prepareAddForm() {
document.getElementById('productImagePreview').style.display = 'none';
}
function prepareEditForm(prod) {
if (!prod) return;
document.getElementById('productModalTitle').innerText = '<?= t('edit') ?> <?= t('products') ?>: ' + prod.name;
function prepareEditForm(p) {
if (!p) return;
document.getElementById('productModalTitle').innerText = '<?= t('edit') ?>: ' + 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';

View File

@ -81,14 +81,22 @@ if (isset($_GET['delete'])) {
if (!has_permission('purchases_del')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete purchases.</div>';
} 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 = '<div class="alert alert-danger">Error deleting purchase: ' . $e->getMessage() . '</div>';
}
}
}
if (isset($_GET['msg']) && $_GET['msg'] === 'deleted') {
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success alert-dismissible fade show" role="alert">Purchase record deleted successfully!<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
}

View File

@ -27,13 +27,21 @@ if (isset($_GET['delete'])) {
if (!has_permission('settings')) { // Use settings permission for deletion
$message = '<div class="alert alert-danger">Access Denied.</div>';
} 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 = '<div class="alert alert-danger">Error deleting rating: ' . $e->getMessage() . '</div>';
}
}
}
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Rating deleted successfully!</div>';
}
$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

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete suppliers.</div>';
} 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 = '<div class="alert alert-danger">Error removing supplier: ' . $e->getMessage() . '</div>';
}
}
}
$query = "SELECT * FROM suppliers ORDER BY name ASC";
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Supplier removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('suppliers_del')): ?>
<a href="?delete=<?= $supplier['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this supplier?')"><i class="bi bi-trash"></i></a>
<a href="?delete=<?= $supplier['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('<?= t('are_you_sure') ?>')"><i class="bi bi-trash"></i></a>
<?php endif; ?>
</td>
</tr>

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete tables.</div>';
} 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 = '<div class="alert alert-danger">Error removing table: ' . $e->getMessage() . '</div>';
}
}
}
$areas = $pdo->query("SELECT * FROM areas ORDER BY name ASC")->fetchAll();
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">Table removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('tables_del')): ?>
<a href="?delete=<?= $table['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this table?')"><i class="bi bi-trash"></i></a>
<a href="?delete=<?= $table['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('<?= t('are_you_sure') ?>')"><i class="bi bi-trash"></i></a>
<?php endif; ?>
</td>
</tr>

View File

@ -33,22 +33,34 @@ if (isset($_GET['delete'])) {
if (!has_permission('user_groups_del')) {
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete user groups.</div>';
} 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();
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 = '<div class="alert alert-danger">The Administrator group cannot be deleted.</div>';
} else {
$pdo->prepare("DELETE FROM user_groups WHERE id = ?")->execute([$id]);
header("Location: user_groups.php");
exit;
if ($groupName === 'Administrator') {
$message = '<div class="alert alert-danger">The Administrator group cannot be deleted.</div>';
} 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 = '<div class="alert alert-danger">Cannot delete this group because it is linked to users.</div>';
} else {
$message = '<div class="alert alert-danger">Error deleting group: ' . $e->getMessage() . '</div>';
}
}
}
}
if (isset($_GET['deleted'])) {
$message = '<div class="alert alert-success">User group deleted successfully!</div>';
}
$availablePermissions = [
'dashboard' => 'Dashboard',
'pos' => 'POS Terminal',

View File

@ -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 = '<div class="alert alert-danger">Access Denied: You do not have permission to delete users.</div>';
} else {
$id = $_GET['delete'];
$id = (int)$_GET['delete'];
// Don't allow deleting current user
if ($id == $_SESSION['user']['id']) {
$message = '<div class="alert alert-danger text-center">You cannot delete your own account.</div>';
$message = '<div class="alert alert-danger text-center">You cannot remove your own account.</div>';
} 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 = '<div class="alert alert-danger">Error removing user: ' . $e->getMessage() . '</div>';
}
}
}
}
$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 = '<div class="alert alert-success">User removed successfully!</div>';
}
$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';
<?php endif; ?>
<?php if (has_permission('users_del')): ?>
<a href="?delete=<?= $user['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Permanently delete this user account?')">
<a href="?delete=<?= $user['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('<?= t('are_you_sure') ?>')">
<i class="bi bi-trash"></i>
</a>
<?php endif; ?>

View File

@ -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

View File

@ -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";
}

View File

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

View File

@ -0,0 +1,2 @@
-- Soft delete for product variants
ALTER TABLE product_variants ADD COLUMN is_deleted TINYINT(1) DEFAULT 0;

View File

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

12
pos.php
View File

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

View File

@ -51,6 +51,9 @@ $users = $stmt->fetchAll(PDO::FETCH_ASSOC);
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+Arabic:wght@400;600;700&display=swap" rel="stylesheet">
<?php if ($success): ?>
<meta http-equiv="refresh" content="3;url=rate.php">
<?php endif; ?>
<style>
:root {
--primary-color: #007bff;
@ -265,7 +268,7 @@ $users = $stmt->fetchAll(PDO::FETCH_ASSOC);
<i class="bi bi-check-circle-fill success-icon"></i>
<h2 data-t="thank_you">Thank You!</h2>
<p data-t="success_msg">Your rating has been submitted successfully.</p>
<a href="index.php" class="btn btn-outline-primary rounded-pill px-4 mt-3" data-t="back_home">Back to Home</a>
<a href="rate.php" class="btn btn-outline-primary rounded-pill px-4 mt-3" data-t="back_home">Rate Again</a>
</div>
</div>
<?php else: ?>
@ -359,7 +362,7 @@ const translations = {
main_instruction: "We value your feedback! What would you like to rate?",
thank_you: "Thank You!",
success_msg: "Your rating has been submitted successfully.",
back_home: "Back to Home",
back_home: "Rate Again",
rate_services: "Rate Our Services",
service_subtitle: "How was your overall experience with us?",
or_rate_staff: "OR RATE OUR STAFF",
@ -380,7 +383,7 @@ const translations = {
main_instruction: "نحن نقدر رأيك! ما الذي تود تقييمه؟",
thank_you: "شكراً لك!",
success_msg: "تم إرسال تقييمك بنجاح.",
back_home: "العودة للرئيسية",
back_home: "تقييم مرة أخرى",
rate_services: "قيم خدماتنا",
service_subtitle: "كيف كانت تجربتك العامة معنا؟",
or_rate_staff: "أو قيم موظفينا",