diff --git a/admin/ads.php b/admin/ads.php
index 49a61e1..d85cd67 100644
--- a/admin/ads.php
+++ b/admin/ads.php
@@ -1,4 +1,6 @@
prepare("SELECT COUNT(*) FROM expenses WHERE category_id = ?");
+ $stmt->execute([$id]);
+ if ($stmt->fetchColumn() > 0) {
+ $_SESSION['error'] = "Cannot delete category as it has linked expenses.";
+ } else {
+ $pdo->prepare("DELETE FROM expense_categories WHERE id = ?")->execute([$id]);
+ $_SESSION['success'] = "Category deleted successfully.";
+ }
+ header("Location: expense_categories.php");
+ exit;
+}
+
+$query = "SELECT * FROM expense_categories ORDER BY name ASC";
+$categories_pagination = paginate_query($pdo, $query);
+$categories = $categories_pagination['data'];
+
+include 'includes/header.php';
+?>
+
+
+
Expense Categories
+
+ Add Category
+
+
+
+
+ = $_SESSION['error']; unset($_SESSION['error']); ?>
+
+
+ = $_SESSION['success']; unset($_SESSION['success']); ?>
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ Name
+ Description
+ Actions
+
+
+
+
+
+ #= $cat['id'] ?>
+ = htmlspecialchars($cat['name']) ?>
+ = htmlspecialchars($cat['description'] ?? '') ?>
+
+
+
+
+
+
+
+
+ No categories found.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/expense_category_edit.php b/admin/expense_category_edit.php
new file mode 100644
index 0000000..702a662
--- /dev/null
+++ b/admin/expense_category_edit.php
@@ -0,0 +1,88 @@
+prepare("SELECT * FROM expense_categories WHERE id = ?");
+ $stmt->execute([$id]);
+ $category = $stmt->fetch();
+ if ($category) {
+ $isEdit = true;
+ } else {
+ header("Location: expense_categories.php");
+ exit;
+ }
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $name = trim($_POST['name']);
+ $description = trim($_POST['description']);
+
+ if (empty($name)) {
+ $message = 'Category name is required.
';
+ } else {
+ try {
+ if ($isEdit) {
+ $stmt = $pdo->prepare("UPDATE expense_categories SET name = ?, description = ? WHERE id = ?");
+ $stmt->execute([$name, $description, $id]);
+ $message = 'Category updated successfully!
';
+ $stmt = $pdo->prepare("SELECT * FROM expense_categories WHERE id = ?");
+ $stmt->execute([$id]);
+ $category = $stmt->fetch();
+ } else {
+ $stmt = $pdo->prepare("INSERT INTO expense_categories (name, description) VALUES (?, ?)");
+ $stmt->execute([$name, $description]);
+ header("Location: expense_categories.php?success=created");
+ exit;
+ }
+ } catch (PDOException $e) {
+ $message = 'Database error: ' . $e->getMessage() . '
';
+ }
+ }
+}
+
+if (!$isEdit) {
+ $category = [
+ 'name' => $_POST['name'] ?? '',
+ 'description' => $_POST['description'] ?? ''
+ ];
+}
+
+include 'includes/header.php';
+?>
+
+
+
+= $message ?>
+
+
+
+
diff --git a/admin/expense_edit.php b/admin/expense_edit.php
new file mode 100644
index 0000000..4b3c8e6
--- /dev/null
+++ b/admin/expense_edit.php
@@ -0,0 +1,133 @@
+prepare("SELECT * FROM expenses WHERE id = ?");
+ $stmt->execute([$id]);
+ $expense = $stmt->fetch();
+ if ($expense) {
+ $isEdit = true;
+ } else {
+ header("Location: expenses.php");
+ exit;
+ }
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $category_id = $_POST['category_id'];
+ $outlet_id = $_POST['outlet_id'];
+ $amount = $_POST['amount'];
+ $description = trim($_POST['description']);
+ $expense_date = $_POST['expense_date'];
+
+ if (empty($category_id) || empty($amount) || empty($expense_date)) {
+ $message = 'Category, amount, and date are required.
';
+ } else {
+ try {
+ if ($isEdit) {
+ $stmt = $pdo->prepare("UPDATE expenses SET category_id = ?, outlet_id = ?, amount = ?, description = ?, expense_date = ? WHERE id = ?");
+ $stmt->execute([$category_id, $outlet_id, $amount, $description, $expense_date, $id]);
+ $message = 'Expense updated successfully!
';
+ $stmt = $pdo->prepare("SELECT * FROM expenses WHERE id = ?");
+ $stmt->execute([$id]);
+ $expense = $stmt->fetch();
+ } else {
+ $stmt = $pdo->prepare("INSERT INTO expenses (category_id, outlet_id, amount, description, expense_date) VALUES (?, ?, ?, ?, ?)");
+ $stmt->execute([$category_id, $outlet_id, $amount, $description, $expense_date]);
+ header("Location: expenses.php?success=created");
+ exit;
+ }
+ } catch (PDOException $e) {
+ $message = 'Database error: ' . $e->getMessage() . '
';
+ }
+ }
+}
+
+if (!$isEdit) {
+ $expense = [
+ 'category_id' => $_POST['category_id'] ?? '',
+ 'outlet_id' => $_POST['outlet_id'] ?? '',
+ 'amount' => $_POST['amount'] ?? '',
+ 'description' => $_POST['description'] ?? '',
+ 'expense_date' => $_POST['expense_date'] ?? date('Y-m-d')
+ ];
+}
+
+$expense_categories = $pdo->query("SELECT * FROM expense_categories ORDER BY name ASC")->fetchAll();
+$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name ASC")->fetchAll();
+
+include 'includes/header.php';
+?>
+
+
+
+= $message ?>
+
+
+
+
+
+
+
+ Category *
+
+ Select Category
+
+ >= htmlspecialchars($cat['name']) ?>
+
+
+
+
+
+
+ Outlet *
+
+
+ >= htmlspecialchars($outlet['name']) ?>
+
+
+
+
+
+
+
+ Description
+ = htmlspecialchars($expense['description']) ?>
+
+
+
+
Cancel
+
= $isEdit ? 'Save Changes' : 'Record Expense' ?>
+
+
+
+
+
+
diff --git a/admin/expenses.php b/admin/expenses.php
new file mode 100644
index 0000000..f376a5e
--- /dev/null
+++ b/admin/expenses.php
@@ -0,0 +1,157 @@
+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");
+ exit;
+ }
+}
+
+$expense_categories = $pdo->query("SELECT * FROM expense_categories ORDER BY name")->fetchAll();
+$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
+
+$search = $_GET['search'] ?? '';
+$category_filter = $_GET['category_filter'] ?? '';
+$outlet_filter = $_GET['outlet_filter'] ?? '';
+
+$params = [];
+$where = [];
+
+$query = "SELECT e.*, ec.name as category_name, o.name as outlet_name
+ FROM expenses e
+ LEFT JOIN expense_categories ec ON e.category_id = ec.id
+ LEFT JOIN outlets o ON e.outlet_id = o.id";
+
+if ($search) {
+ $where[] = "e.description LIKE ?";
+ $params[] = "%$search%";
+}
+
+if ($category_filter) {
+ $where[] = "e.category_id = ?";
+ $params[] = $category_filter;
+}
+
+if ($outlet_filter) {
+ $where[] = "e.outlet_id = ?";
+ $params[] = $outlet_filter;
+}
+
+if (!empty($where)) {
+ $query .= " WHERE " . implode(" AND ", $where);
+}
+
+$query .= " ORDER BY e.expense_date DESC, e.id DESC";
+
+$expenses_pagination = paginate_query($pdo, $query, $params);
+$expenses = $expenses_pagination['data'];
+
+include 'includes/header.php';
+?>
+
+
+
+
Expenses
+
Track and manage business expenditures
+
+
+
+ Add Expense
+
+
+
+
+= $message ?>
+
+
+
+
+
+
+
+
+
+ All Categories
+
+ >= htmlspecialchars($cat['name']) ?>
+
+
+
+
+
+ All Outlets
+
+ >= htmlspecialchars($outlet['name']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Date
+ Category
+ Outlet
+ Description
+ Amount
+ Actions
+
+
+
+
+
+ = date('M d, Y', strtotime($exp['expense_date'])) ?>
+ = htmlspecialchars($exp['category_name']) ?>
+ = htmlspecialchars($exp['outlet_name']) ?>
+ = htmlspecialchars($exp['description']) ?>
+ = format_currency($exp['amount']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ No expenses found.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/includes/header.php b/admin/includes/header.php
index 5e720eb..5362e72 100644
--- a/admin/includes/header.php
+++ b/admin/includes/header.php
@@ -43,6 +43,12 @@ function isGroupExpanded($pages) {
function getGroupToggleClass($pages) {
return in_array(basename($_SERVER['PHP_SELF']), $pages) ? '' : 'collapsed';
}
+
+// Permission helper for sidebar
+function can_view($module) {
+ if (!function_exists('has_permission')) return true;
+ return has_permission($module . '_view');
+}
?>
@@ -223,14 +229,20 @@ function getGroupToggleClass($pages) {
diff --git a/admin/index.php b/admin/index.php
index c328e74..1209cf7 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -3,6 +3,7 @@ require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/functions.php';
$pdo = db();
+require_permission('dashboard_view');
// Fetch Dashboard Stats
$today = date('Y-m-d');
@@ -36,9 +37,11 @@ include 'includes/header.php';
Dashboard
Welcome back, = htmlspecialchars($userName) ?>!
+
+
@@ -51,7 +54,7 @@ include 'includes/header.php';
Today's Revenue
- $= number_format($revenueToday, 2) ?>
+ = format_currency($revenueToday) ?>
@@ -144,7 +147,7 @@ include 'includes/header.php';
= htmlspecialchars($order['customer_name'] ?? 'Guest') ?>
- $= number_format((float)$order['total_amount'], 2) ?>
+ = format_currency($order['total_amount']) ?>
= ucfirst($order['status']) ?>
@@ -160,9 +163,11 @@ include 'includes/header.php';
+
+
diff --git a/admin/integrations.php b/admin/integrations.php
index 98319d5..759cac9 100644
--- a/admin/integrations.php
+++ b/admin/integrations.php
@@ -1,4 +1,6 @@
prepare("UPDATE loyalty_settings SET points_per_order = ?, points_for_free_meal = ? WHERE id = 1");
- $stmt->execute([$points_per_order, $points_for_free_meal]);
+ $stmt = $pdo->prepare("UPDATE loyalty_settings SET points_per_order = ?, points_for_free_meal = ?, is_enabled = ? WHERE id = 1");
+ $stmt->execute([$points_per_order, $points_for_free_meal, $is_enabled]);
$success_msg = "Loyalty settings updated successfully!";
}
@@ -19,7 +22,7 @@ $settings = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$settings) {
// Default fallback if migration failed or empty
- $settings = ['points_per_order' => 10, 'points_for_free_meal' => 70];
+ $settings = ['points_per_order' => 10, 'points_for_free_meal' => 70, 'is_enabled' => 1];
}
// Fetch Customers with Points
@@ -39,7 +42,14 @@ include 'includes/header.php';
?>
-
Loyalty Program
+
+
Loyalty Program
+
+ Active
+
+ Disabled
+
+
Configure Settings
@@ -166,6 +176,15 @@ include 'includes/header.php';
+
+
+
+ Enable Loyalty Program
+ style="width: 3em; height: 1.5em; cursor: pointer;">
+
+
When disabled, loyalty features will be hidden from the POS.
+
+
Points per Order
@@ -185,4 +204,4 @@ include 'includes/header.php';
-
+
\ No newline at end of file
diff --git a/admin/orders.php b/admin/orders.php
index 858597b..8278aab 100644
--- a/admin/orders.php
+++ b/admin/orders.php
@@ -1,10 +1,16 @@
prepare("UPDATE orders SET status = ? WHERE id = ?");
@@ -77,6 +83,10 @@ include 'includes/header.php';
+
+Access Denied: You do not have permission to perform this action.
+
+
@@ -260,6 +270,7 @@ include 'includes/header.php';
= date('H:i', strtotime($order['created_at'])) ?>
+
@@ -283,6 +294,9 @@ include 'includes/header.php';
-
+
+ View Only
+
@@ -304,4 +318,4 @@ include 'includes/header.php';
-
\ No newline at end of file
+
diff --git a/admin/outlets.php b/admin/outlets.php
index e2bd32a..f6cec63 100644
--- a/admin/outlets.php
+++ b/admin/outlets.php
@@ -1,4 +1,6 @@
prepare("UPDATE products SET name = ?, category_id = ?, price = ?, description = ?, image_url = ? WHERE id = ?");
- if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $id])) {
+ $stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?");
+ if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id])) {
$message = 'Product updated successfully!
';
// Refresh product data
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
@@ -105,6 +110,28 @@ include 'includes/header.php';
+
+
+
+
Promotion Settings
+
+
If active, this discount will be automatically applied to the regular price.
+
+
+
Description
= htmlspecialchars($product['description']) ?>
@@ -133,4 +160,4 @@ include 'includes/header.php';
-
\ No newline at end of file
+
diff --git a/admin/product_variants.php b/admin/product_variants.php
index bb0f9f8..3fe296d 100644
--- a/admin/product_variants.php
+++ b/admin/product_variants.php
@@ -1,5 +1,6 @@
@@ -98,8 +101,11 @@ include 'includes/header.php';
No change
-
- = format_currency($product['price'] + $variant['price_adjustment']) ?>
+
+ = format_currency($effective_base_price + $variant['price_adjustment']) ?>
+
+ = format_currency($product['price'] + $variant['price_adjustment']) ?>
+
-
+
\ No newline at end of file
diff --git a/admin/products.php b/admin/products.php
index 6b758d6..e02b7d7 100644
--- a/admin/products.php
+++ b/admin/products.php
@@ -1,53 +1,68 @@
Access Denied: You do not have permission to add products.';
+ } else {
+ $name = $_POST['name'];
+ $category_id = $_POST['category_id'];
+ $price = $_POST['price'];
+ $description = $_POST['description'];
- $fileInfo = pathinfo($_FILES['image']['name']);
- $fileExt = strtolower($fileInfo['extension']);
- $allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+ $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 handling
+ $image_url = 'https://placehold.co/400x300?text=' . urlencode($name);
- if (in_array($fileExt, $allowedExts)) {
- $fileName = uniqid('prod_') . '.' . $fileExt;
- $targetFile = $uploadDir . $fileName;
+ if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
+ $uploadDir = __DIR__ . '/../assets/images/products/';
+ if (!is_dir($uploadDir)) {
+ mkdir($uploadDir, 0755, true);
+ }
- if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
- $image_url = 'assets/images/products/' . $fileName;
+ $fileInfo = pathinfo($_FILES['image']['name']);
+ $fileExt = strtolower($fileInfo['extension']);
+ $allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+
+ if (in_array($fileExt, $allowedExts)) {
+ $fileName = uniqid('prod_') . '.' . $fileExt;
+ $targetFile = $uploadDir . $fileName;
+
+ if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
+ $image_url = 'assets/images/products/' . $fileName;
+ }
}
}
- }
- $stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, description, image_url) VALUES (?, ?, ?, ?, ?)");
- if ($stmt->execute([$name, $category_id, $price, $description, $image_url])) {
- $message = ' Product added successfully!
';
- } else {
- $message = ' Error adding product.
';
+ $stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
+ if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) {
+ $message = ' Product added successfully!
';
+ } else {
+ $message = ' Error adding product.
';
+ }
}
}
// Handle Delete
if (isset($_GET['delete'])) {
- $id = $_GET['delete'];
- $pdo->prepare("DELETE FROM products WHERE id = ?")->execute([$id]);
- header("Location: products.php");
- exit;
+ 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;
+ }
}
// Fetch Categories for Dropdown (moved up for filter usage)
@@ -92,9 +107,11 @@ include 'includes/header.php';
Products
Manage your catalog
+
Add Product
+
= $message ?>
@@ -142,11 +159,20 @@ include 'includes/header.php';
Product
Category
Price
+ Promotion
Actions
-
+ = $product['promo_date_from'] &&
+ $today <= $product['promo_date_to'];
+ ?>
@@ -161,19 +187,47 @@ include 'includes/header.php';
= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?>
- = format_currency($product['price']) ?>
+
+
+ = format_currency($discounted_price) ?>
+ = format_currency($product['price']) ?>
+
+ = format_currency($product['price']) ?>
+
+
+
+
+ Active (-= floatval($product['promo_discount_percent']) ?>%)
+ Ends = $product['promo_date_to'] ?>
+
+
+ Upcoming
+ Starts = $product['promo_date_from'] ?>
+
+ Expired
+
+
+ -
+
@@ -189,8 +243,9 @@ include 'includes/header.php';
+