From 4bd6115a47ceb4b2a8800c210bc16514eb05a072 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 23 Feb 2026 15:06:36 +0000 Subject: [PATCH] Autosave: 20260223-150636 --- admin/includes/header.php | 8 +- admin/index.php | 405 ++++++++++++++---- admin/product_edit.php | 27 +- admin/products.php | 22 +- admin/user_edit.php | 116 +++-- .../018_add_cost_price_to_products.sql | 2 + 6 files changed, 437 insertions(+), 143 deletions(-) create mode 100644 db/migrations/018_add_cost_price_to_products.sql diff --git a/admin/includes/header.php b/admin/includes/header.php index 23bf47f..a935e67 100644 --- a/admin/includes/header.php +++ b/admin/includes/header.php @@ -472,15 +472,15 @@ function can_view($module) { + - Roles / Groups - - - diff --git a/admin/index.php b/admin/index.php index bc7d0a4..98edba2 100644 --- a/admin/index.php +++ b/admin/index.php @@ -12,6 +12,7 @@ $isDetailed = has_permission('dashboard_add') || has_permission('all'); if ($isDetailed) { // Fetch Dashboard Stats $today = date('Y-m-d'); + $thisMonth = date('Y-m'); // Total Revenue Today $stmt = $pdo->prepare("SELECT SUM(total_amount) FROM orders WHERE DATE(created_at) = ? AND status != 'cancelled'"); @@ -19,16 +20,75 @@ if ($isDetailed) { $revenueToday = $stmt->fetchColumn() ?: 0; // Total Orders Today - $stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE DATE(created_at) = ?"); + $stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE DATE(created_at) = ? AND status != 'cancelled'"); $stmt->execute([$today]); $ordersToday = $stmt->fetchColumn(); + // Total Revenue This Month + $stmt = $pdo->prepare("SELECT SUM(total_amount) FROM orders WHERE DATE_FORMAT(created_at, '%Y-%m') = ? AND status != 'cancelled'"); + $stmt->execute([$thisMonth]); + $revenueThisMonth = $stmt->fetchColumn() ?: 0; + + // Total Expenses This Month + $stmt = $pdo->prepare("SELECT SUM(amount) FROM expenses WHERE DATE_FORMAT(expense_date, '%Y-%m') = ?"); + $stmt->execute([$thisMonth]); + $expensesThisMonth = $stmt->fetchColumn() ?: 0; + + // Estimated Net Profit This Month + $netProfitThisMonth = $revenueThisMonth - $expensesThisMonth; + // Active Outlets $outletsCount = $pdo->query("SELECT COUNT(*) FROM outlets")->fetchColumn(); // Total Products $productsCount = $pdo->query("SELECT COUNT(*) FROM products")->fetchColumn(); + // 1. Sales Trend (Last 12 Months) + $salesTrendQuery = " + SELECT DATE_FORMAT(created_at, '%b %Y') as month_label, SUM(total_amount) as total, DATE_FORMAT(created_at, '%Y-%m') as sort_key + FROM orders + WHERE status != 'cancelled' + AND created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH) + GROUP BY month_label, sort_key + ORDER BY sort_key ASC"; + $salesTrend = $pdo->query($salesTrendQuery)->fetchAll(); + + // 2. Sales by Category (Pie Chart) + $salesByCategoryQuery = " + SELECT c.name as category_name, SUM(oi.quantity * oi.unit_price) as total_sales + FROM order_items oi + JOIN products p ON oi.product_id = p.id + JOIN categories c ON p.category_id = c.id + JOIN orders o ON oi.order_id = o.id + WHERE o.status != 'cancelled' + GROUP BY c.name + ORDER BY total_sales DESC"; + $salesByCategory = $pdo->query($salesByCategoryQuery)->fetchAll(); + + // 3. Sales by Order Type (Pie Chart) + $salesByTypeQuery = " + SELECT order_type, SUM(total_amount) as total + FROM orders + WHERE status != 'cancelled' + GROUP BY order_type"; + $salesByType = $pdo->query($salesByTypeQuery)->fetchAll(); + + // 4. Top 5 Items Sold + $topItemsQuery = " + SELECT p.name, SUM(oi.quantity) as total_qty + FROM order_items oi + JOIN products p ON oi.product_id = p.id + JOIN orders o ON oi.order_id = o.id + WHERE o.status != 'cancelled' + GROUP BY p.name + ORDER BY total_qty DESC + LIMIT 5"; + $topItems = $pdo->query($topItemsQuery)->fetchAll(); + + // 5. Value Add: Average Order Value + $stmt = $pdo->query("SELECT AVG(total_amount) FROM orders WHERE status != 'cancelled'"); + $avgOrderValue = $stmt->fetchColumn() ?: 0; + // Recent Orders $recentOrders = $pdo->query("SELECT o.*, (SELECT GROUP_CONCAT(p.name SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items @@ -44,29 +104,49 @@ include 'includes/header.php';

Dashboard

Welcome back, !

- -
- New Order +
+ + Reports + + + New Order +
-
-
-
- -
-
-
Today's Revenue
-

+
+
+
+ +
+
+
Today's Revenue
+

+
+ +
+
+
+
+ +
+
+
Profit (This Month)
+

+
+
+
+
+
@@ -82,100 +162,251 @@ include 'includes/header.php';
- +
- +
-
Active Outlets
-

-
-
-
-
- - -
-
-
-
- -
-
-
Total Products
-

+
Avg. Order Value
+

- -
-
-
Recent Orders
+
+ +
+
+
+
Sales Trend
+ Last 12 Months +
+
+ +
+
-
-
- - - - - - - - - - - - - - - - - - - - - + + +
+
+
+
+
+
Category Distribution
+
+
+ +

No data available

+ + + +
+
+
+
+
+
+
Order Type Distribution
+
+
+ +

No data available

+ + + +
+
+
+
+
+ + +
+ +
+
+
+
Top 5 Items Sold (Qty)
+
+
+
    + $item): ?> +
  • +
    + + +
    + Sold +
  • - -
+ +
  • No sales data yet.
  • - -
    IDTypeTable/CustomerTotalStatusDate
    # - 'bg-info', - 'takeaway' => 'bg-success', - 'delivery' => 'bg-warning', - 'drive-thru' => 'bg-purple', - default => 'bg-secondary' - }; - ?> - - - - Table - - - - - - - -
    No recent orders found.
    + +
    - - + + + + +
    diff --git a/admin/product_edit.php b/admin/product_edit.php index a233abe..53f376f 100644 --- a/admin/product_edit.php +++ b/admin/product_edit.php @@ -25,6 +25,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = $_POST['name']; $category_id = $_POST['category_id']; $price = $_POST['price']; + $cost_price = $_POST['cost_price'] ?: 0; $description = $_POST['description']; $image_url = $product['image_url']; // Default to existing @@ -59,8 +60,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } if (empty($message)) { - $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])) { + $stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, cost_price = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?"); + if ($stmt->execute([$name, $category_id, $price, $cost_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 = ?"); @@ -91,12 +92,12 @@ include 'includes/header.php';
    - +
    -
    +
    - +
    - +
    +
    +
    + + +
    +
    @@ -133,19 +140,19 @@ include 'includes/header.php';
    - +
    - +
    Product Image
    - +
    Leave empty to keep current image.
    @@ -160,4 +167,4 @@ include 'includes/header.php';
    - + \ No newline at end of file diff --git a/admin/products.php b/admin/products.php index e02b7d7..40d934d 100644 --- a/admin/products.php +++ b/admin/products.php @@ -15,6 +15,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' $name = $_POST['name']; $category_id = $_POST['category_id']; $price = $_POST['price']; + $cost_price = $_POST['cost_price'] ?: 0; $description = $_POST['description']; $promo_discount_percent = $_POST['promo_discount_percent'] !== '' ? $_POST['promo_discount_percent'] : null; @@ -44,8 +45,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' } } - $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])) { + $stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, cost_price, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + if ($stmt->execute([$name, $category_id, $price, $cost_price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) { $message = '
    Product added successfully!
    '; } else { $message = '
    Error adding product.
    '; @@ -158,7 +159,8 @@ include 'includes/header.php'; Product Category - Price + Cost Price + Selling Price Promotion Actions @@ -186,6 +188,9 @@ include 'includes/header.php'; + + +
    - +
    $
    +
    +
    + +
    + $ + +
    +
    +
    diff --git a/admin/user_edit.php b/admin/user_edit.php index 86a9cd9..ebffd8b 100644 --- a/admin/user_edit.php +++ b/admin/user_edit.php @@ -12,11 +12,23 @@ if ($id) { $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); $user = $stmt->fetch(PDO::FETCH_ASSOC); -} - -if (!$user) { - header('Location: users.php'); - exit; + if (!$user) { + header('Location: users.php'); + exit; + } +} else { + // Default values for new user + $user = [ + 'id' => null, + 'username' => '', + 'full_name' => '', + 'email' => '', + 'group_id' => '', + 'employee_id' => '', + 'is_active' => 1, + 'profile_pic' => '', + 'created_at' => date('Y-m-d H:i:s') + ]; } $message = ''; @@ -29,11 +41,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $employee_id = $_POST['employee_id'] ?? null; $is_active = isset($_POST['is_active']) ? 1 : 0; $assigned_outlets = $_POST['outlets'] ?? []; + $password = $_POST['password'] ?? ''; - // Check if username changed and if new one exists - if ($username !== $user['username']) { + // Validation + if (!$id && empty($password)) { + $message = '
    Password is required for new users.
    '; + } else { + // Check if username already exists $stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? AND id != ?"); - $stmt->execute([$username, $id]); + $stmt->execute([$username, (int)$id]); if ($stmt->fetch()) { $message = '
    Username already taken.
    '; } @@ -42,24 +58,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!$message) { $pdo->beginTransaction(); try { - $sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, employee_id = ? WHERE id = ?"; - $params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $id]; - - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - - // Update password if provided - if (!empty($_POST['password'])) { - $password = password_hash($_POST['password'], PASSWORD_DEFAULT); - $pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$password, $id]); + if ($id) { + // Update + $sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, employee_id = ? WHERE id = ?"; + $params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $id]; + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + + if (!empty($password)) { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$hashed_password, $id]); + } + $user_id = $id; + } else { + // Insert + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $sql = "INSERT INTO users (full_name, username, email, group_id, is_active, employee_id, password) VALUES (?, ?, ?, ?, ?, ?, ?)"; + $params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $hashed_password]; + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $user_id = $pdo->lastInsertId(); } // Update assigned outlets - $pdo->prepare("DELETE FROM user_outlets WHERE user_id = ?")->execute([$id]); + $pdo->prepare("DELETE FROM user_outlets WHERE user_id = ?")->execute([$user_id]); if (!empty($assigned_outlets)) { $stmt_outlet = $pdo->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)"); foreach ($assigned_outlets as $outlet_id) { - $stmt_outlet->execute([$id, $outlet_id]); + $stmt_outlet->execute([$user_id, $outlet_id]); } } @@ -76,7 +102,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $allowed_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; if (in_array($file_ext, $allowed_exts)) { - $new_file_name = 'user_' . $id . '_' . uniqid() . '.' . $file_ext; + $new_file_name = 'user_' . $user_id . '_' . uniqid() . '.' . $file_ext; $upload_path = $upload_dir . $new_file_name; if (move_uploaded_file($file_tmp, $upload_path)) { @@ -86,30 +112,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } $profile_pic_path = 'assets/images/users/' . $new_file_name; - $pdo->prepare("UPDATE users SET profile_pic = ? WHERE id = ?")->execute([$profile_pic_path, $id]); + $pdo->prepare("UPDATE users SET profile_pic = ? WHERE id = ?")->execute([$profile_pic_path, $user_id]); } } } $pdo->commit(); - $message = '
    User updated successfully!
    '; + + if ($id) { + $message = '
    User updated successfully!
    '; + } else { + header("Location: user_edit.php?id=$user_id&success=1"); + exit; + } // Refresh user data $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); - $stmt->execute([$id]); + $stmt->execute([$user_id]); $user = $stmt->fetch(PDO::FETCH_ASSOC); } catch (Exception $e) { $pdo->rollBack(); - $message = '
    Error updating user: ' . $e->getMessage() . '
    '; + $message = '
    Error saving user: ' . $e->getMessage() . '
    '; } } } +if (isset($_GET['success'])) { + $message = '
    User created successfully!
    '; +} + $groups = $pdo->query("SELECT * FROM user_groups ORDER BY name")->fetchAll(); $all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll(); -$user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?"); -$user_outlets->execute([$id]); -$assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN); +$assigned_outlet_ids = []; +if ($id) { + $user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?"); + $user_outlets->execute([$id]); + $assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN); +} include 'includes/header.php'; ?> @@ -117,7 +156,7 @@ include 'includes/header.php';
    Back to Users
    -

    Edit User:

    +

    User

    @@ -146,9 +185,10 @@ include 'includes/header.php';
    - + + + >
    @@ -208,7 +248,7 @@ include 'includes/header.php';
    Cancel - +
    @@ -224,17 +264,17 @@ include 'includes/header.php'; Profile Picture
    - +
    -
    +
    Member since
    -
    -
    User ID: #
    +
    +
    User ID: New' ?>

    diff --git a/db/migrations/018_add_cost_price_to_products.sql b/db/migrations/018_add_cost_price_to_products.sql new file mode 100644 index 0000000..e074bf3 --- /dev/null +++ b/db/migrations/018_add_cost_price_to_products.sql @@ -0,0 +1,2 @@ +-- Add cost_price to products table +ALTER TABLE products ADD COLUMN cost_price DECIMAL(10, 2) DEFAULT 0.00;