modifying loyalty
This commit is contained in:
parent
3595e1b23e
commit
370ceb510e
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("ads_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("areas_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("categories_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("settings_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("customers_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
84
admin/expense_categories.php
Normal file
84
admin/expense_categories.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("expense_categories_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = $_GET['delete'];
|
||||
// Check if there are expenses linked to this category
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Expense Categories</h2>
|
||||
<a href="expense_category_edit.php" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg"></i> Add Category
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div class="alert alert-danger"><?= $_SESSION['error']; unset($_SESSION['error']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['success'])): ?>
|
||||
<div class="alert alert-success"><?= $_SESSION['success']; unset($_SESSION['success']); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="p-3 border-bottom bg-light">
|
||||
<?php render_pagination_controls($categories_pagination); ?>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $cat['id'] ?></td>
|
||||
<td><?= htmlspecialchars($cat['name']) ?></td>
|
||||
<td><?= htmlspecialchars($cat['description'] ?? '') ?></td>
|
||||
<td class="text-end pe-4">
|
||||
<a href="expense_category_edit.php?id=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-primary me-1"><i class="bi bi-pencil"></i></a>
|
||||
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')"><i class="bi bi-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($categories)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">No categories found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-3 border-top bg-light">
|
||||
<?php render_pagination_controls($categories_pagination); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
88
admin/expense_category_edit.php
Normal file
88
admin/expense_category_edit.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("expense_categories_edit");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
$category = null;
|
||||
$message = '';
|
||||
$isEdit = false;
|
||||
|
||||
if ($id) {
|
||||
$stmt = $pdo->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 = '<div class="alert alert-danger">Category name is required.</div>';
|
||||
} else {
|
||||
try {
|
||||
if ($isEdit) {
|
||||
$stmt = $pdo->prepare("UPDATE expense_categories SET name = ?, description = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $description, $id]);
|
||||
$message = '<div class="alert alert-success">Category updated successfully!</div>';
|
||||
$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 = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isEdit) {
|
||||
$category = [
|
||||
'name' => $_POST['name'] ?? '',
|
||||
'description' => $_POST['description'] ?? ''
|
||||
];
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="expense_categories.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Categories</a>
|
||||
<h2 class="fw-bold mb-0"><?= $isEdit ? 'Edit Category' : 'Add New Category' ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category Name <span class="text-danger">*</span></label>
|
||||
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($category['name']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($category['description'] ?? '') ?></textarea>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="expense_categories.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary"><?= $isEdit ? 'Save Changes' : 'Create Category' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
133
admin/expense_edit.php
Normal file
133
admin/expense_edit.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("expenses_edit");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
$expense = null;
|
||||
$message = '';
|
||||
$isEdit = false;
|
||||
|
||||
if ($id) {
|
||||
$stmt = $pdo->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 = '<div class="alert alert-danger">Category, amount, and date are required.</div>';
|
||||
} 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 = '<div class="alert alert-success">Expense updated successfully!</div>';
|
||||
$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 = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="expenses.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Expenses</a>
|
||||
<h2 class="fw-bold mb-0"><?= $isEdit ? 'Edit Expense' : 'Add New Expense' ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category <span class="text-danger">*</span></label>
|
||||
<select name="category_id" class="form-select" required>
|
||||
<option value="">Select Category</option>
|
||||
<?php foreach ($expense_categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $expense['category_id'] == $cat['id'] ? 'selected' : '' ?>><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Outlet <span class="text-danger">*</span></label>
|
||||
<select name="outlet_id" class="form-select" required>
|
||||
<?php foreach ($outlets as $outlet): ?>
|
||||
<option value="<?= $outlet['id'] ?>" <?= $expense['outlet_id'] == $outlet['id'] ? 'selected' : '' ?>><?= htmlspecialchars($outlet['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Amount <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" name="amount" class="form-control" value="<?= htmlspecialchars($expense['amount']) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Date <span class="text-danger">*</span></label>
|
||||
<input type="date" name="expense_date" class="form-control" value="<?= htmlspecialchars($expense['expense_date']) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3" placeholder="What was this expense for?"><?= htmlspecialchars($expense['description']) ?></textarea>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="expenses.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary"><?= $isEdit ? 'Save Changes' : 'Record Expense' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
157
admin/expenses.php
Normal file
157
admin/expenses.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("expenses_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$message = '';
|
||||
|
||||
if (isset($_GET['delete'])) {
|
||||
if (!has_permission('expenses_delete')) {
|
||||
$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");
|
||||
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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1">Expenses</h2>
|
||||
<p class="text-muted mb-0">Track and manage business expenditures</p>
|
||||
</div>
|
||||
<?php if (has_permission('expenses_add')): ?>
|
||||
<a href="expense_edit.php" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg"></i> Add Expense
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search description..." value="<?= htmlspecialchars($search) ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="category_filter" class="form-select">
|
||||
<option value="">All Categories</option>
|
||||
<?php foreach ($expense_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">
|
||||
<select name="outlet_filter" class="form-select">
|
||||
<option value="">All Outlets</option>
|
||||
<?php foreach ($outlets as $outlet): ?>
|
||||
<option value="<?= $outlet['id'] ?>" <?= $outlet_filter == $outlet['id'] ? 'selected' : '' ?>><?= htmlspecialchars($outlet['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-outline-primary w-100">Filter</button>
|
||||
<?php if ($search || $category_filter || $outlet_filter): ?>
|
||||
<a href="expenses.php" class="btn btn-outline-secondary"><i class="bi bi-x"></i></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="p-3 border-bottom bg-light">
|
||||
<?php render_pagination_controls($expenses_pagination); ?>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">Date</th>
|
||||
<th>Category</th>
|
||||
<th>Outlet</th>
|
||||
<th>Description</th>
|
||||
<th>Amount</th>
|
||||
<th class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($expenses as $exp): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium"><?= date('M d, Y', strtotime($exp['expense_date'])) ?></td>
|
||||
<td><span class="badge bg-info bg-opacity-10 text-info"><?= htmlspecialchars($exp['category_name']) ?></span></td>
|
||||
<td><?= htmlspecialchars($exp['outlet_name']) ?></td>
|
||||
<td><?= htmlspecialchars($exp['description']) ?></td>
|
||||
<td class="fw-bold"><?= format_currency($exp['amount']) ?></td>
|
||||
<td class="text-end pe-4">
|
||||
<?php if (has_permission('expenses_edit')): ?>
|
||||
<a href="expense_edit.php?id=<?= $exp['id'] ?>" class="btn btn-sm btn-outline-primary me-1"><i class="bi bi-pencil"></i></a>
|
||||
<?php endif; ?>
|
||||
<?php if (has_permission('expenses_delete')): ?>
|
||||
<a href="?delete=<?= $exp['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')"><i class="bi bi-trash"></i></a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($expenses)): ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4 text-muted">No expenses found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-3 border-top bg-light">
|
||||
<?php render_pagination_controls($expenses_pagination); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.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');
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@ -223,14 +229,20 @@ function getGroupToggleClass($pages) {
|
||||
|
||||
<div class="sidebar-content accordion" id="sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('dashboard')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('index.php') ?>" href="index.php">
|
||||
<i class="bi bi-grid me-2"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
|
||||
<?php $posGroup = ['orders.php', 'ads.php', 'ad_edit.php']; ?>
|
||||
<?php
|
||||
$posGroup = ['orders.php', 'ads.php', 'ad_edit.php'];
|
||||
$canViewPosGroup = can_view('pos') || can_view('orders') || can_view('kitchen') || can_view('ads');
|
||||
if ($canViewPosGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($posGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapsePos" role="button" aria-expanded="<?= isGroupExpanded($posGroup) ?>" aria-controls="collapsePos">
|
||||
@ -239,31 +251,44 @@ function getGroupToggleClass($pages) {
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($posGroup) ?>" id="collapsePos" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('pos')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="../pos.php" target="_blank">
|
||||
<i class="bi bi-display me-2"></i> POS Terminal
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('orders')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('orders.php') ?>" href="orders.php">
|
||||
<i class="bi bi-receipt me-2"></i> Orders (POS)
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('kitchen')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="../kitchen.php" target="_blank">
|
||||
<i class="bi bi-fire me-2"></i> Kitchen View
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('ads')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('ads.php') || isActive('ad_edit.php') ? 'active' : '' ?>" href="ads.php">
|
||||
<i class="bi bi-megaphone me-2"></i> Ads Management
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $menuGroup = ['products.php', 'product_edit.php', 'categories.php', 'category_edit.php', 'product_variants.php']; ?>
|
||||
<?php
|
||||
$menuGroup = ['products.php', 'product_edit.php', 'categories.php', 'category_edit.php', 'product_variants.php'];
|
||||
$canViewMenuGroup = can_view('products') || can_view('categories');
|
||||
if ($canViewMenuGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($menuGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapseMenu" role="button" aria-expanded="<?= isGroupExpanded($menuGroup) ?>" aria-controls="collapseMenu">
|
||||
@ -272,21 +297,30 @@ function getGroupToggleClass($pages) {
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($menuGroup) ?>" id="collapseMenu" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('products')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('products.php') ?>" href="products.php">
|
||||
<i class="bi bi-box-seam me-2"></i> Products
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('categories')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('categories.php') ?>" href="categories.php">
|
||||
<i class="bi bi-tags me-2"></i> Categories
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $setupGroup = ['outlets.php', 'outlet_edit.php', 'areas.php', 'area_edit.php', 'tables.php', 'table_edit.php']; ?>
|
||||
<?php
|
||||
$setupGroup = ['outlets.php', 'outlet_edit.php', 'areas.php', 'area_edit.php', 'tables.php', 'table_edit.php'];
|
||||
$canViewSetupGroup = can_view('outlets') || can_view('areas') || can_view('tables');
|
||||
if ($canViewSetupGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($setupGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapseSetup" role="button" aria-expanded="<?= isGroupExpanded($setupGroup) ?>" aria-controls="collapseSetup">
|
||||
@ -295,26 +329,37 @@ function getGroupToggleClass($pages) {
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($setupGroup) ?>" id="collapseSetup" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('outlets')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('outlets.php') ?>" href="outlets.php">
|
||||
<i class="bi bi-shop me-2"></i> Outlets
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('areas')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('areas.php') ?>" href="areas.php">
|
||||
<i class="bi bi-geo-alt me-2"></i> Areas
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('tables')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('tables.php') ?>" href="tables.php">
|
||||
<i class="bi bi-ui-checks-grid me-2"></i> Tables
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $peopleGroup = ['customers.php', 'customer_edit.php', 'suppliers.php', 'supplier_edit.php', 'loyalty.php']; ?>
|
||||
<?php
|
||||
$peopleGroup = ['customers.php', 'customer_edit.php', 'suppliers.php', 'supplier_edit.php', 'loyalty.php'];
|
||||
$canViewPeopleGroup = can_view('customers') || can_view('suppliers') || can_view('loyalty');
|
||||
if ($canViewPeopleGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($peopleGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapsePeople" role="button" aria-expanded="<?= isGroupExpanded($peopleGroup) ?>" aria-controls="collapsePeople">
|
||||
@ -323,27 +368,91 @@ function getGroupToggleClass($pages) {
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($peopleGroup) ?>" id="collapsePeople" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('customers')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('customers.php') ?>" href="customers.php">
|
||||
<i class="bi bi-people-fill me-2"></i> Customers
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('suppliers')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('suppliers.php') ?>" href="suppliers.php">
|
||||
<i class="bi bi-truck me-2"></i> Suppliers
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('loyalty')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('loyalty.php') ?>" href="loyalty.php">
|
||||
<i class="bi bi-award me-2"></i> Loyalty
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $reportsGroup = ['reports.php']; ?> <div class="nav-group"> <a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($reportsGroup) ?>" data-bs-toggle="collapse" href="#collapseReports" role="button" aria-expanded="<?= isGroupExpanded($reportsGroup) ?>" aria-controls="collapseReports"> <span><i class="bi bi-bar-chart-line"></i> Reports & Analytics</span> <i class="bi bi-chevron-down chevron-icon"></i> </a> <div class="collapse <?= isGroupActive($reportsGroup) ?>" id="collapseReports" data-bs-parent="#sidebarAccordion"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link <?= isActive('reports.php') ?>" href="reports.php"> <i class="bi bi-graph-up me-2"></i> Daily Reports </a> </li> </ul> </div> </div>
|
||||
<?php $userGroupPages = ['users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php']; ?>
|
||||
<?php
|
||||
$financialsGroup = ['expenses.php', 'expense_edit.php', 'expense_categories.php', 'expense_category_edit.php'];
|
||||
$canViewFinancialsGroup = can_view('expenses') || can_view('expense_categories');
|
||||
if ($canViewFinancialsGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($financialsGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapseFinancials" role="button" aria-expanded="<?= isGroupExpanded($financialsGroup) ?>" aria-controls="collapseFinancials">
|
||||
<span><i class="bi bi-wallet2"></i> Financials</span>
|
||||
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($financialsGroup) ?>" id="collapseFinancials" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('expenses')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('expenses.php') || isActive('expense_edit.php') ? 'active' : '' ?>" href="expenses.php">
|
||||
<i class="bi bi-cash-stack me-2"></i> Expenses
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('expense_categories')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('expense_categories.php') || isActive('expense_category_edit.php') ? 'active' : '' ?>" href="expense_categories.php">
|
||||
<i class="bi bi-tags me-2"></i> Expense Categories
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$reportsGroup = ['reports.php'];
|
||||
if (can_view('reports')):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($reportsGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapseReports" role="button" aria-expanded="<?= isGroupExpanded($reportsGroup) ?>" aria-controls="collapseReports">
|
||||
<span><i class="bi bi-bar-chart-line"></i> Reports & Analytics</span>
|
||||
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($reportsGroup) ?>" id="collapseReports" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('reports.php') ?>" href="reports.php">
|
||||
<i class="bi bi-graph-up me-2"></i> Daily Reports
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$userGroupPages = ['users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php'];
|
||||
$canViewUserGroup = can_view('users') || can_view('user_groups');
|
||||
if ($canViewUserGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($userGroupPages) ?>"
|
||||
data-bs-toggle="collapse" href="#collapseUsers" role="button" aria-expanded="<?= isGroupExpanded($userGroupPages) ?>" aria-controls="collapseUsers">
|
||||
@ -352,21 +461,30 @@ function getGroupToggleClass($pages) {
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($userGroupPages) ?>" id="collapseUsers" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (can_view('users')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('users.php') ?>" href="users.php">
|
||||
<i class="bi bi-people me-2"></i> Users
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('user_groups')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php">
|
||||
<i class="bi bi-shield-lock me-2"></i> Roles / Groups
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $settingsGroup = ['payment_types.php', 'payment_type_edit.php', 'integrations.php', 'company.php']; ?>
|
||||
<?php
|
||||
$settingsGroup = ['payment_types.php', 'payment_type_edit.php', 'integrations.php', 'company.php'];
|
||||
$canViewSettingsGroup = can_view('payment_types') || can_view('settings');
|
||||
if ($canViewSettingsGroup):
|
||||
?>
|
||||
<div class="nav-group">
|
||||
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($settingsGroup) ?>"
|
||||
data-bs-toggle="collapse" href="#collapseSettings" role="button" aria-expanded="<?= isGroupExpanded($settingsGroup) ?>" aria-controls="collapseSettings">
|
||||
@ -375,11 +493,14 @@ function getGroupToggleClass($pages) {
|
||||
</a>
|
||||
<div class="collapse <?= isGroupActive($settingsGroup) ?>" id="collapseSettings" data-bs-parent="#sidebarAccordion">
|
||||
<ul class="nav flex-column mb-5">
|
||||
<?php if (can_view('payment_types')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('payment_types.php') ?>" href="payment_types.php">
|
||||
<i class="bi bi-credit-card me-2"></i> Payment Types
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('settings')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('integrations.php') ?>" href="integrations.php">
|
||||
<i class="bi bi-plugin me-2"></i> Integrations
|
||||
@ -390,6 +511,7 @@ function getGroupToggleClass($pages) {
|
||||
<i class="bi bi-building me-2"></i> Company
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<li class="nav-item border-top mt-2 pt-2">
|
||||
<a class="nav-link text-muted" href="../index.php" target="_blank">
|
||||
@ -399,6 +521,7 @@ function getGroupToggleClass($pages) {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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';
|
||||
<h2 class="fw-bold mb-1">Dashboard</h2>
|
||||
<p class="text-muted">Welcome back, <?= htmlspecialchars($userName) ?>!</p>
|
||||
</div>
|
||||
<?php if (has_permission('orders_add')): ?>
|
||||
<div>
|
||||
<a href="orders.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a>
|
||||
<a href="../pos.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
@ -51,7 +54,7 @@ include 'includes/header.php';
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Today's Revenue</h6>
|
||||
<h3 class="fw-bold mb-0">$<?= number_format($revenueToday, 2) ?></h3>
|
||||
<h3 class="fw-bold mb-0"><?= format_currency($revenueToday) ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -144,7 +147,7 @@ include 'includes/header.php';
|
||||
<?= htmlspecialchars($order['customer_name'] ?? 'Guest') ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="fw-bold">$<?= number_format((float)$order['total_amount'], 2) ?></td>
|
||||
<td class="fw-bold"><?= format_currency($order['total_amount']) ?></td>
|
||||
<td>
|
||||
<span class="status-badge status-<?= $order['status'] ?> badge rounded-pill">
|
||||
<?= ucfirst($order['status']) ?>
|
||||
@ -160,9 +163,11 @@ include 'includes/header.php';
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (has_permission('orders_view')): ?>
|
||||
<div class="card-footer bg-white text-center py-3">
|
||||
<a href="orders.php" class="text-decoration-none fw-medium">View All Orders</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("settings_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/WablasService.php';
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("loyalty_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
@ -6,9 +8,10 @@ $pdo = db();
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_settings'])) {
|
||||
$points_per_order = intval($_POST['points_per_order']);
|
||||
$points_for_free_meal = intval($_POST['points_for_free_meal']);
|
||||
$is_enabled = isset($_POST['is_enabled']) ? 1 : 0;
|
||||
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Loyalty Program</h2>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<h2 class="fw-bold mb-0">Loyalty Program</h2>
|
||||
<?php if ($settings['is_enabled']): ?>
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle px-3">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-danger-subtle text-danger border border-danger-subtle px-3">Disabled</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
<i class="bi bi-gear-fill me-2"></i> Configure Settings
|
||||
</button>
|
||||
@ -166,6 +176,15 @@ include 'includes/header.php';
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="update_settings" value="1">
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check form-switch p-0 d-flex justify-content-between align-items-center">
|
||||
<label class="form-check-label fw-bold" for="is_enabled">Enable Loyalty Program</label>
|
||||
<input class="form-check-input ms-0" type="checkbox" id="is_enabled" name="is_enabled" <?= $settings['is_enabled'] ? 'checked' : '' ?> style="width: 3em; height: 1.5em; cursor: pointer;">
|
||||
</div>
|
||||
<div class="form-text mt-1">When disabled, loyalty features will be hidden from the POS.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Points per Order</label>
|
||||
<input type="number" name="points_per_order" class="form-control" value="<?= $settings['points_per_order'] ?>" min="0" required>
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
$pdo = db();
|
||||
require_permission('orders_view');
|
||||
|
||||
if (isset($_POST['action']) && $_POST['action'] === 'update_status') {
|
||||
if (!has_permission('orders_add')) {
|
||||
header("Location: orders.php?error=permission_denied");
|
||||
exit;
|
||||
}
|
||||
$order_id = $_POST['order_id'];
|
||||
$new_status = $_POST['status'];
|
||||
$stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?");
|
||||
@ -77,6 +83,10 @@ include 'includes/header.php';
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_GET['error']) && $_GET['error'] === 'permission_denied'): ?>
|
||||
<div class="alert alert-danger border-0 shadow-sm rounded-3">Access Denied: You do not have permission to perform this action.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
@ -260,6 +270,7 @@ include 'includes/header.php';
|
||||
<div><?= date('H:i', strtotime($order['created_at'])) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (has_permission('orders_add')): ?>
|
||||
<form method="POST" class="d-flex gap-2">
|
||||
<input type="hidden" name="order_id" value="<?= $order['id'] ?>">
|
||||
<input type="hidden" name="action" value="update_status">
|
||||
@ -283,6 +294,9 @@ include 'includes/header.php';
|
||||
<span class="text-muted small">-</span>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">View Only</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("outlets_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("payment_types_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -28,6 +28,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$description = $_POST['description'];
|
||||
$image_url = $product['image_url']; // Default to existing
|
||||
|
||||
// Promo fields
|
||||
$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
|
||||
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = __DIR__ . '/../assets/images/products/';
|
||||
@ -54,8 +59,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
if (empty($message)) {
|
||||
$stmt = $pdo->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 = '<div class="alert alert-success">Product updated successfully!</div>';
|
||||
// Refresh product data
|
||||
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
|
||||
@ -105,6 +110,28 @@ include 'includes/header.php';
|
||||
<input type="number" step="0.01" name="price" class="form-control" value="<?= htmlspecialchars($product['price']) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light border-0 mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-tag-fill me-2 text-primary"></i>Promotion Settings</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label small fw-bold text-muted">DISCOUNT PERCENT (%)</label>
|
||||
<input type="number" step="0.01" name="promo_discount_percent" class="form-control" value="<?= htmlspecialchars($product['promo_discount_percent'] ?? '') ?>" placeholder="e.g. 10.00">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label small fw-bold text-muted">START DATE</label>
|
||||
<input type="date" name="promo_date_from" class="form-control" value="<?= htmlspecialchars($product['promo_date_from'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label small fw-bold text-muted">END DATE</label>
|
||||
<input type="date" name="promo_date_to" class="form-control" value="<?= htmlspecialchars($product['promo_date_to'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text mt-0">If active, this discount will be automatically applied to the regular price.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="5"><?= htmlspecialchars($product['description']) ?></textarea>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
$pdo = db();
|
||||
|
||||
if (!isset($_GET['product_id'])) {
|
||||
@ -54,6 +55,8 @@ $variants_pagination = paginate_query($pdo, $query, [$product_id]);
|
||||
$variants = $variants_pagination['data'];
|
||||
|
||||
include 'includes/header.php';
|
||||
|
||||
$effective_base_price = get_product_price($product);
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
@ -98,8 +101,11 @@ include 'includes/header.php';
|
||||
<span class="text-muted">No change</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-muted">
|
||||
<?= format_currency($product['price'] + $variant['price_adjustment']) ?>
|
||||
<td>
|
||||
<div class="fw-bold"><?= format_currency($effective_base_price + $variant['price_adjustment']) ?></div>
|
||||
<?php if ($effective_base_price < $product['price']): ?>
|
||||
<small class="text-muted text-decoration-line-through"><?= format_currency($product['price'] + $variant['price_adjustment']) ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1"
|
||||
|
||||
@ -1,53 +1,68 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
$pdo = db();
|
||||
require_permission('products_view');
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Add Product
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_product') {
|
||||
$name = $_POST['name'];
|
||||
$category_id = $_POST['category_id'];
|
||||
$price = $_POST['price'];
|
||||
$description = $_POST['description'];
|
||||
if (!has_permission('products_add')) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3">Access Denied: You do not have permission to add products.</div>';
|
||||
} else {
|
||||
$name = $_POST['name'];
|
||||
$category_id = $_POST['category_id'];
|
||||
$price = $_POST['price'];
|
||||
$description = $_POST['description'];
|
||||
|
||||
// Image handling
|
||||
$image_url = 'https://placehold.co/400x300?text=' . urlencode($name);
|
||||
$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;
|
||||
|
||||
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = __DIR__ . '/../assets/images/products/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
// Image handling
|
||||
$image_url = 'https://placehold.co/400x300?text=' . urlencode($name);
|
||||
|
||||
$fileInfo = pathinfo($_FILES['image']['name']);
|
||||
$fileExt = strtolower($fileInfo['extension']);
|
||||
$allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = __DIR__ . '/../assets/images/products/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
if (in_array($fileExt, $allowedExts)) {
|
||||
$fileName = uniqid('prod_') . '.' . $fileExt;
|
||||
$targetFile = $uploadDir . $fileName;
|
||||
$fileInfo = pathinfo($_FILES['image']['name']);
|
||||
$fileExt = strtolower($fileInfo['extension']);
|
||||
$allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
|
||||
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
|
||||
$image_url = 'assets/images/products/' . $fileName;
|
||||
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 = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Product added successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding product.</div>';
|
||||
$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 = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Product added successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding product.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = '<div class="alert alert-danger border-0 shadow-sm rounded-3">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;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Categories for Dropdown (moved up for filter usage)
|
||||
@ -92,9 +107,11 @@ include 'includes/header.php';
|
||||
<h2 class="fw-bold mb-1 text-dark">Products</h2>
|
||||
<p class="text-muted mb-0">Manage your catalog</p>
|
||||
</div>
|
||||
<?php if (has_permission('products_add')): ?>
|
||||
<button class="btn btn-primary btn-lg shadow-sm" data-bs-toggle="modal" data-bs-target="#addProductModal" style="border-radius: 10px; padding: 0.6rem 1.2rem;">
|
||||
<i class="bi bi-plus-lg me-1"></i> Add Product
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
@ -142,11 +159,20 @@ include 'includes/header.php';
|
||||
<th>Product</th>
|
||||
<th>Category</th>
|
||||
<th>Price</th>
|
||||
<th>Promotion</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<?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'];
|
||||
?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
@ -161,19 +187,47 @@ include 'includes/header.php';
|
||||
<span class="badge-soft"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-price fs-5"><?= format_currency($product['price']) ?></span>
|
||||
<?php if ($is_promo_active): ?>
|
||||
<?php
|
||||
$discounted_price = $product['price'] * (1 - ($product['promo_discount_percent'] / 100));
|
||||
?>
|
||||
<div class="text-price fs-5"><?= format_currency($discounted_price) ?></div>
|
||||
<div class="text-muted small text-decoration-line-through"><?= format_currency($product['price']) ?></div>
|
||||
<?php else: ?>
|
||||
<span class="text-price fs-5"><?= format_currency($product['price']) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($is_promo_active): ?>
|
||||
<span class="badge bg-success rounded-pill">Active (-<?= floatval($product['promo_discount_percent']) ?>%)</span>
|
||||
<div class="small text-muted mt-1">Ends <?= $product['promo_date_to'] ?></div>
|
||||
<?php elseif (!empty($product['promo_discount_percent'])): ?>
|
||||
<?php if ($today < $product['promo_date_from']): ?>
|
||||
<span class="badge bg-info rounded-pill">Upcoming</span>
|
||||
<div class="small text-muted mt-1">Starts <?= $product['promo_date_from'] ?></div>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary rounded-pill">Expired</span>
|
||||
<?php endif; ?>
|
||||
<?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_add')): ?>
|
||||
<a href="product_edit.php?id=<?= $product['id'] ?>" class="btn-icon-soft edit" title="Edit Product">
|
||||
<i class="bi bi-pencil-fill" style="font-size: 0.9rem;"></i>
|
||||
</a>
|
||||
<a href="product_variants.php?product_id=<?= $product['id'] ?>" class="btn-icon-soft" title="Manage Variants">
|
||||
<i class="bi bi-sliders" style="font-size: 0.9rem;"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (has_permission('products_del')): ?>
|
||||
<a href="?delete=<?= $product['id'] ?>" class="btn-icon-soft delete" onclick="return confirm('Are you sure you want to delete this product?')" title="Delete">
|
||||
<i class="bi bi-trash-fill" style="font-size: 0.9rem;"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -189,8 +243,9 @@ include 'includes/header.php';
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Add Product Modal -->
|
||||
<?php if (has_permission('products_add')): ?>
|
||||
<div class="modal fade" id="addProductModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
|
||||
<div class="modal-header border-bottom-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">Add New Product</h5>
|
||||
@ -199,32 +254,55 @@ include 'includes/header.php';
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-body pt-4">
|
||||
<input type="hidden" name="action" value="add_product">
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">PRODUCT NAME</label>
|
||||
<input type="text" name="name" class="form-control form-control-lg" placeholder="e.g. Cheese Burger" required style="border-radius: 10px;">
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small fw-bold">CATEGORY</label>
|
||||
<select name="category_id" class="form-select" required style="border-radius: 10px;">
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">PRODUCT NAME</label>
|
||||
<input type="text" name="name" class="form-control form-control-lg" placeholder="e.g. Cheese Burger" required style="border-radius: 10px;">
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small fw-bold">CATEGORY</label>
|
||||
<select name="category_id" class="form-select" required style="border-radius: 10px;">
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small fw-bold">PRICE ($)</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0" style="border-radius: 10px 0 0 10px;">$</span>
|
||||
<input type="number" step="0.01" name="price" class="form-control border-start-0" placeholder="0.00" required style="border-radius: 0 10px 10px 0;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">DESCRIPTION</label>
|
||||
<textarea name="description" class="form-control" rows="3" placeholder="Brief description..." style="border-radius: 10px;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small fw-bold">PRICE ($)</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0" style="border-radius: 10px 0 0 10px;">$</span>
|
||||
<input type="number" step="0.01" name="price" class="form-control border-start-0" placeholder="0.00" required style="border-radius: 0 10px 10px 0;">
|
||||
<div class="col-md-5">
|
||||
<div class="card bg-light border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-tag-fill me-2 text-primary"></i>Promotion</h6>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">DISCOUNT (%)</label>
|
||||
<input type="number" step="0.01" name="promo_discount_percent" class="form-control" placeholder="0.00" style="border-radius: 10px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">START DATE</label>
|
||||
<input type="date" name="promo_date_from" class="form-control" style="border-radius: 10px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">END DATE</label>
|
||||
<input type="date" name="promo_date_to" class="form-control" style="border-radius: 10px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">DESCRIPTION</label>
|
||||
<textarea name="description" class="form-control" rows="3" placeholder="Brief description..." style="border-radius: 10px;"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="mb-3 mt-3">
|
||||
<label class="form-label text-muted small fw-bold">IMAGE</label>
|
||||
<input type="file" name="image" class="form-control" accept="image/*" style="border-radius: 10px;">
|
||||
<div class="form-text">Optional. Placeholder used if empty.</div>
|
||||
@ -238,5 +316,6 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -3,14 +3,29 @@ require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
// Check permission
|
||||
if (function_exists('require_permission')) {
|
||||
require_permission('manage_reports');
|
||||
require_permission('reports_view');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Date Filter
|
||||
// Date and Outlet Filter
|
||||
$startDate = $_GET['start_date'] ?? date('Y-m-d');
|
||||
$endDate = $_GET['end_date'] ?? date('Y-m-d');
|
||||
$outletId = $_GET['outlet_id'] ?? '';
|
||||
|
||||
// Fetch Outlets for filter
|
||||
$outletsStmt = $pdo->query("SELECT id, name FROM outlets ORDER BY name ASC");
|
||||
$allOutlets = $outletsStmt->fetchAll();
|
||||
|
||||
// Base query additions
|
||||
$outletCondition = "";
|
||||
$expenseOutletCondition = "";
|
||||
$queryParams = [$startDate, $endDate];
|
||||
if (!empty($outletId)) {
|
||||
$outletCondition = " AND o.outlet_id = ? ";
|
||||
$expenseOutletCondition = " AND e.outlet_id = ? ";
|
||||
$queryParams[] = $outletId;
|
||||
}
|
||||
|
||||
// 1. Sales by Staff
|
||||
$staffStmt = $pdo->prepare("
|
||||
@ -22,10 +37,11 @@ $staffStmt = $pdo->prepare("
|
||||
FROM orders o
|
||||
JOIN users u ON o.user_id = u.id
|
||||
WHERE DATE(o.created_at) BETWEEN ? AND ?
|
||||
$outletCondition
|
||||
GROUP BY o.user_id
|
||||
ORDER BY total_sales DESC
|
||||
");
|
||||
$staffStmt->execute([$startDate, $endDate]);
|
||||
$staffStmt->execute($queryParams);
|
||||
$staffSales = $staffStmt->fetchAll();
|
||||
|
||||
// 2. Sales by Outlet
|
||||
@ -37,10 +53,11 @@ $outletStmt = $pdo->prepare("
|
||||
FROM orders o
|
||||
JOIN outlets ot ON o.outlet_id = ot.id
|
||||
WHERE DATE(o.created_at) BETWEEN ? AND ?
|
||||
$outletCondition
|
||||
GROUP BY o.outlet_id
|
||||
ORDER BY total_sales DESC
|
||||
");
|
||||
$outletStmt->execute([$startDate, $endDate]);
|
||||
$outletStmt->execute($queryParams);
|
||||
$outletSales = $outletStmt->fetchAll();
|
||||
|
||||
// 3. Sales by Category
|
||||
@ -54,35 +71,87 @@ $categoryStmt = $pdo->prepare("
|
||||
JOIN products p ON oi.product_id = p.id
|
||||
JOIN categories c ON p.category_id = c.id
|
||||
WHERE DATE(o.created_at) BETWEEN ? AND ?
|
||||
$outletCondition
|
||||
GROUP BY c.id
|
||||
ORDER BY total_sales DESC
|
||||
");
|
||||
$categoryStmt->execute([$startDate, $endDate]);
|
||||
$categoryStmt->execute($queryParams);
|
||||
$categorySales = $categoryStmt->fetchAll();
|
||||
|
||||
// 4. Expenses by Category
|
||||
$expenseCatStmt = $pdo->prepare("
|
||||
SELECT
|
||||
ec.name as category_name,
|
||||
SUM(e.amount) as total_amount
|
||||
FROM expenses e
|
||||
JOIN expense_categories ec ON e.category_id = ec.id
|
||||
WHERE e.expense_date BETWEEN ? AND ?
|
||||
$expenseOutletCondition
|
||||
GROUP BY ec.id
|
||||
ORDER BY total_amount DESC
|
||||
");
|
||||
$expenseCatStmt->execute($queryParams);
|
||||
$expenseSales = $expenseCatStmt->fetchAll();
|
||||
|
||||
// Total Summary
|
||||
$totalStmt = $pdo->prepare("
|
||||
SELECT
|
||||
COUNT(id) as total_orders,
|
||||
SUM(total_amount) as total_revenue
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN ? AND ?
|
||||
FROM orders o
|
||||
WHERE DATE(o.created_at) BETWEEN ? AND ?
|
||||
$outletCondition
|
||||
");
|
||||
$totalStmt->execute([$startDate, $endDate]);
|
||||
$totalStmt->execute($queryParams);
|
||||
$summary = $totalStmt->fetch();
|
||||
|
||||
$totalExpStmt = $pdo->prepare("
|
||||
SELECT SUM(amount) as total_expenses
|
||||
FROM expenses e
|
||||
WHERE expense_date BETWEEN ? AND ?
|
||||
$expenseOutletCondition
|
||||
");
|
||||
$totalExpStmt->execute($queryParams);
|
||||
$totalExpenses = $totalExpStmt->fetchColumn() ?? 0;
|
||||
|
||||
$netProfit = ($summary['total_revenue'] ?? 0) - $totalExpenses;
|
||||
?>
|
||||
|
||||
<div class="container-fluid p-0">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1">Daily Reports</h2>
|
||||
<p class="text-muted mb-0">Overview of your business performance</p>
|
||||
</div>
|
||||
<form class="d-flex gap-2" method="GET">
|
||||
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($startDate) ?>">
|
||||
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($endDate) ?>">
|
||||
<button type="submit" class="btn btn-primary">Filter</button>
|
||||
<form class="row g-2 align-items-center" method="GET">
|
||||
<div class="col-auto">
|
||||
<select name="outlet_id" class="form-select" style="min-width: 180px;">
|
||||
<option value="">All Outlets</option>
|
||||
<?php foreach ($allOutlets as $o): ?>
|
||||
<option value="<?= $o['id'] ?>" <?= $outletId == $o['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted"><i class="bi bi-calendar"></i></span>
|
||||
<input type="date" name="start_date" class="form-control border-start-0" value="<?= htmlspecialchars($startDate) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted"><i class="bi bi-calendar"></i></span>
|
||||
<input type="date" name="end_date" class="form-control border-start-0" value="<?= htmlspecialchars($endDate) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary px-4 text-nowrap">Filter</button>
|
||||
<a href="reports.php" class="btn btn-light text-nowrap">Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -114,6 +183,32 @@ $summary = $totalStmt->fetch();
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card border-0 shadow-sm stat-card p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="icon-box bg-danger-subtle text-danger">
|
||||
<i class="bi bi-cash-stack"></i>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted d-block">Total Expenses</small>
|
||||
<h4 class="fw-bold mb-0"><?= format_currency($totalExpenses) ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card border-0 shadow-sm stat-card p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="icon-box bg-info-subtle text-info">
|
||||
<i class="bi bi-piggy-bank"></i>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted d-block">Net Profit</small>
|
||||
<h4 class="fw-bold mb-0 <?= $netProfit < 0 ? 'text-danger' : 'text-info' ?>"><?= format_currency($netProfit) ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
@ -192,8 +287,8 @@ $summary = $totalStmt->fetch();
|
||||
</div>
|
||||
|
||||
<!-- Sales by Category -->
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white py-3 border-0">
|
||||
<h5 class="fw-bold mb-0"><i class="bi bi-tags me-2 text-warning"></i> Sales by Category</h5>
|
||||
</div>
|
||||
@ -227,6 +322,41 @@ $summary = $totalStmt->fetch();
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expenses by Category -->
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white py-3 border-0">
|
||||
<h5 class="fw-bold mb-0"><i class="bi bi-cash-stack me-2 text-danger"></i> Expenses by Category</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-3">Category</th>
|
||||
<th class="text-end pe-3">Total Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($expenseSales)): ?>
|
||||
<tr><td colspan="2" class="text-center py-4 text-muted">No data found for this period</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($expenseSales as $exp): ?>
|
||||
<tr>
|
||||
<td class="ps-3">
|
||||
<div class="fw-bold"><?= htmlspecialchars($exp['category_name']) ?></div>
|
||||
</td>
|
||||
<td class="text-end pe-3 fw-bold text-danger"><?= format_currency($exp['total_amount']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("suppliers_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("tables_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
|
||||
208
admin/user_group_edit.php
Normal file
208
admin/user_group_edit.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
$pdo = db();
|
||||
require_permission('user_groups_view');
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
$group = null;
|
||||
|
||||
// Handle New Group Creation via POST from user_groups.php
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$id && isset($_POST['name'])) {
|
||||
if (!has_permission('user_groups_add')) {
|
||||
header('Location: user_groups.php?error=permission_denied');
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO user_groups (name, permissions) VALUES (?, '')");
|
||||
$stmt->execute([$_POST['name']]);
|
||||
$id = $pdo->lastInsertId();
|
||||
header("Location: user_group_edit.php?id=" . $id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM user_groups WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$group = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
if (!$group) {
|
||||
header('Location: user_groups.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$message = '';
|
||||
|
||||
$modules = [
|
||||
'dashboard' => 'Dashboard',
|
||||
'pos' => 'POS Terminal',
|
||||
'kitchen' => 'Kitchen View',
|
||||
'orders' => 'Orders',
|
||||
'products' => 'Products',
|
||||
'categories' => 'Categories',
|
||||
'customers' => 'Customers',
|
||||
'outlets' => 'Outlets',
|
||||
'areas' => 'Areas',
|
||||
'tables' => 'Tables',
|
||||
'suppliers' => 'Suppliers',
|
||||
'payment_types' => 'Payment Types',
|
||||
'loyalty' => 'Loyalty',
|
||||
'ads' => 'Ads',
|
||||
'reports' => 'Reports',
|
||||
'users' => 'Users',
|
||||
'user_groups' => 'User Groups',
|
||||
'settings' => 'Settings'
|
||||
];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_group') {
|
||||
if (!has_permission('user_groups_add')) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3">Access Denied: You do not have permission to edit groups.</div>';
|
||||
} else {
|
||||
$name = $_POST['name'];
|
||||
$permissions = isset($_POST['perms']) ? implode(',', $_POST['perms']) : '';
|
||||
|
||||
// Check if name is not empty
|
||||
if (empty($name)) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Group name cannot be empty.</div>';
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE user_groups SET name = ?, permissions = ? WHERE id = ?");
|
||||
if ($stmt->execute([$name, $permissions, $id])) {
|
||||
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Group updated successfully!</div>';
|
||||
|
||||
// Refresh group data
|
||||
$stmt = $pdo->prepare("SELECT * FROM user_groups WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$group = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error updating group.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$current_perms = explode(',', $group['permissions']);
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="user_groups.php" class="text-decoration-none text-muted small"><i class="bi bi-arrow-left"></i> Back to Groups</a>
|
||||
<h2 class="fw-bold mt-2">Edit Group: <?= htmlspecialchars($group['name']) ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="update_group">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="mb-4 col-md-4">
|
||||
<label class="form-label small fw-bold text-muted">GROUP NAME</label>
|
||||
<input type="text" name="name" class="form-control form-control-lg border-0 bg-light" value="<?= htmlspecialchars($group['name']) ?>" required style="border-radius: 12px;" <?= !has_permission('user_groups_add') ? 'readonly' : '' ?>>
|
||||
</div>
|
||||
|
||||
<label class="form-label small fw-bold text-muted mb-3">MODULE PERMISSIONS</label>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4 py-3">Module</th>
|
||||
<th class="text-center py-3">View</th>
|
||||
<th class="text-center py-3">Add / Edit</th>
|
||||
<th class="text-center py-3">Delete</th>
|
||||
<th class="text-center py-3">Full Access</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($modules as $key => $label): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium text-dark"><?= $label ?></td>
|
||||
<td class="text-center">
|
||||
<div class="form-check form-check-inline m-0">
|
||||
<input class="form-check-input perm-checkbox" type="checkbox" name="perms[]" value="<?= $key ?>_view" id="perm_<?= $key ?>_view" <?= (in_array($key . '_view', $current_perms) || in_array('all', $current_perms)) ? 'checked' : '' ?> <?= !has_permission('user_groups_add') ? 'disabled' : '' ?>>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="form-check form-check-inline m-0">
|
||||
<input class="form-check-input perm-checkbox" type="checkbox" name="perms[]" value="<?= $key ?>_add" id="perm_<?= $key ?>_add" <?= (in_array($key . '_add', $current_perms) || in_array('all', $current_perms)) ? 'checked' : '' ?> <?= !has_permission('user_groups_add') ? 'disabled' : '' ?>>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="form-check form-check-inline m-0">
|
||||
<input class="form-check-input perm-checkbox" type="checkbox" name="perms[]" value="<?= $key ?>_del" id="perm_<?= $key ?>_del" <?= (in_array($key . '_del', $current_perms) || in_array('all', $current_perms)) ? 'checked' : '' ?> <?= !has_permission('user_groups_add') ? 'disabled' : '' ?>>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?php if (has_permission('user_groups_add')): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill px-3" onclick="toggleRow('<?= $key ?>', true)">All</button>
|
||||
<button type="button" class="btn btn-sm btn-link text-muted text-decoration-none" onclick="toggleRow('<?= $key ?>', false)">None</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<tr class="table-info bg-opacity-10">
|
||||
<td class="ps-4 fw-bold">ADMINISTRATIVE</td>
|
||||
<td colspan="3" class="text-center small text-muted">Grants full access to everything in the system.</td>
|
||||
<td class="text-center">
|
||||
<div class="form-check form-switch d-inline-block">
|
||||
<input class="form-check-input" type="checkbox" name="perms[]" value="all" id="perm_all" <?= in_array('all', $current_perms) ? 'checked' : '' ?> <?= !has_permission('user_groups_add') ? 'disabled' : '' ?>>
|
||||
<label class="form-check-label fw-bold" for="perm_all">Super Admin</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-3 border-top d-flex justify-content-end gap-2">
|
||||
<a href="user_groups.php" class="btn btn-light rounded-pill px-4">Cancel</a>
|
||||
<?php if (has_permission('user_groups_add')): ?>
|
||||
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm">Save Permissions</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function toggleRow(key, state) {
|
||||
document.getElementById('perm_' + key + '_view').checked = state;
|
||||
document.getElementById('perm_' + key + '_add').checked = state;
|
||||
document.getElementById('perm_' + key + '_del').checked = state;
|
||||
}
|
||||
|
||||
// If Super Admin is checked, maybe disable others? Or just let them be.
|
||||
const permAll = document.getElementById('perm_all');
|
||||
if (permAll) {
|
||||
permAll.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
document.querySelectorAll('.perm-checkbox').forEach(cb => {
|
||||
cb.checked = true;
|
||||
cb.disabled = true;
|
||||
});
|
||||
} else {
|
||||
document.querySelectorAll('.perm-checkbox').forEach(cb => {
|
||||
cb.disabled = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Initial state for Super Admin
|
||||
if (permAll.checked) {
|
||||
document.querySelectorAll('.perm-checkbox').forEach(cb => {
|
||||
cb.disabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -3,139 +3,126 @@ require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
$pdo = db();
|
||||
require_permission('all'); // Only super admin can manage groups
|
||||
require_permission('user_groups_view');
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Add Group
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_group') {
|
||||
$name = $_POST['name'];
|
||||
$permissions = isset($_POST['perms']) ? implode(',', $_POST['perms']) : '';
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO user_groups (name, permissions) VALUES (?, ?)");
|
||||
if ($stmt->execute([$name, $permissions])) {
|
||||
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Group added successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding group.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Delete
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = $_GET['delete'];
|
||||
// Prevent deleting the Administrator group (id 1 usually)
|
||||
if ($id != 1) {
|
||||
$pdo->prepare("DELETE FROM user_groups WHERE id = ?")->execute([$id]);
|
||||
if (!has_permission('user_groups_del')) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3">Access Denied: You do not have permission to delete groups.</div>';
|
||||
} else {
|
||||
$id = $_GET['delete'];
|
||||
// Don't delete admin group
|
||||
if ($id == 1) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3">Cannot delete the Administrator group.</div>';
|
||||
} else {
|
||||
$pdo->prepare("DELETE FROM user_groups WHERE id = ?")->execute([$id]);
|
||||
header("Location: user_groups.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
header("Location: user_groups.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY id ASC")->fetchAll();
|
||||
|
||||
$available_perms = [
|
||||
'manage_orders' => 'Manage Orders',
|
||||
'manage_products' => 'Manage Products/Menu',
|
||||
'manage_reports' => 'View Reports',
|
||||
'manage_settings' => 'System Settings',
|
||||
'pos' => 'Access POS Terminal',
|
||||
'kitchen' => 'Access Kitchen View',
|
||||
'all' => 'Full Administrator Access'
|
||||
];
|
||||
// Fetch Groups
|
||||
$groups = $pdo->query("SELECT g.*, (SELECT COUNT(*) FROM users u WHERE u.group_id = g.id) as user_count
|
||||
FROM user_groups g
|
||||
ORDER BY g.id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1 text-dark">User Groups / Roles</h2>
|
||||
<p class="text-muted mb-0">Define what different users can do</p>
|
||||
<h2 class="fw-bold mb-1">User Groups / Roles</h2>
|
||||
<p class="text-muted mb-0">Define permissions and access levels</p>
|
||||
</div>
|
||||
<button class="btn btn-primary shadow-sm rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addGroupModal">
|
||||
<i class="bi bi-plus-lg me-1"></i> Add Group
|
||||
<?php if (has_permission('user_groups_add')): ?>
|
||||
<button class="btn btn-primary btn-lg shadow-sm" data-bs-toggle="modal" data-bs-target="#addGroupModal" style="border-radius: 10px;">
|
||||
<i class="bi bi-shield-plus me-1"></i> Add Group
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-3 overflow-hidden">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Group Name</th>
|
||||
<th>Permissions</th>
|
||||
<th class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<tr>
|
||||
<td class="ps-4"><?= $group['id'] ?></td>
|
||||
<td><span class="fw-bold"><?= htmlspecialchars($group['name']) ?></span></td>
|
||||
<td>
|
||||
<?php
|
||||
if ($group['permissions'] === 'all') {
|
||||
echo '<span class="badge bg-danger rounded-pill px-3">Full Access</span>';
|
||||
} else {
|
||||
$perms = explode(',', $group['permissions']);
|
||||
foreach ($perms as $p) {
|
||||
if (isset($available_perms[$p])) {
|
||||
echo '<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 rounded-pill px-2 me-1 mb-1" style="font-weight:500;">' . $available_perms[$p] . '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<?php if ($group['id'] != 1): ?>
|
||||
<a href="?delete=<?= $group['id'] ?>" class="btn btn-sm btn-outline-danger border-0 rounded-circle" onclick="return confirm('Delete this group?')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row g-4">
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card border-0 shadow-sm rounded-4 h-100 position-relative overflow-hidden">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div class="bg-primary bg-opacity-10 text-primary p-3 rounded-3 shadow-sm">
|
||||
<i class="bi bi-shield-lock-fill fs-4"></i>
|
||||
</div>
|
||||
<?php if (has_permission('user_groups_add')): ?>
|
||||
<a href="user_group_edit.php?id=<?= $group['id'] ?>" class="btn-icon-soft edit" title="Edit Permissions">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<h5 class="fw-bold mb-1"><?= htmlspecialchars($group['name']) ?></h5>
|
||||
<p class="text-muted small mb-3"><?= $group['user_count'] ?> users assigned</p>
|
||||
|
||||
<div class="d-flex flex-wrap gap-1 mb-4">
|
||||
<?php
|
||||
$perms = explode(',', $group['permissions']);
|
||||
$display_perms = array_slice($perms, 0, 3);
|
||||
foreach ($display_perms as $p): if (empty($p)) continue; ?>
|
||||
<span class="badge bg-light text-muted border px-2 py-1" style="font-size: 0.65rem;"><?= htmlspecialchars($p) ?></span>
|
||||
<?php endforeach; ?>
|
||||
<?php if (count($perms) > 3): ?>
|
||||
<span class="badge bg-light text-muted border px-2 py-1" style="font-size: 0.65rem;">+<?= count($perms) - 3 ?> more</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<?php if (has_permission('user_groups_add')): ?>
|
||||
<a href="user_group_edit.php?id=<?= $group['id'] ?>" class="btn btn-primary w-100 rounded-pill">Manage Permissions</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (has_permission('user_groups_del') && $group['id'] != 1): ?>
|
||||
<a href="?delete=<?= $group['id'] ?>" class="btn btn-light text-danger w-100 rounded-pill" onclick="return confirm('Are you sure?')">Delete</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($group['name'] === 'Administrator' || $group['permissions'] === 'all'): ?>
|
||||
<div class="position-absolute top-0 end-0 m-3">
|
||||
<span class="badge bg-warning text-dark shadow-sm">Super Admin</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Add Group Modal -->
|
||||
<?php if (has_permission('user_groups_add')): ?>
|
||||
<div class="modal fade" id="addGroupModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0 px-4 pt-4">
|
||||
<h5 class="modal-title fw-bold">Create New Group</h5>
|
||||
<div class="modal-header border-0 pb-0 ps-4 pt-4">
|
||||
<h5 class="modal-title fw-bold">Create New User Group</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<form action="user_group_edit.php" method="POST">
|
||||
<div class="modal-body p-4">
|
||||
<input type="hidden" name="action" value="add_group">
|
||||
<div class="mb-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-muted">GROUP NAME</label>
|
||||
<input type="text" name="name" class="form-control form-control-lg border-0 bg-light" placeholder="e.g. Supervisor" required style="border-radius: 12px;">
|
||||
</div>
|
||||
<label class="form-label small fw-bold text-muted mb-3">PERMISSIONS</label>
|
||||
<div class="row g-2">
|
||||
<?php foreach ($available_perms as $key => $label): ?>
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch p-3 bg-light rounded-3 d-flex align-items-center justify-content-between">
|
||||
<label class="form-check-label ms-0 fw-medium" for="perm_<?= $key ?>"><?= $label ?></label>
|
||||
<input class="form-check-input" type="checkbox" name="perms[]" value="<?= $key ?>" id="perm_<?= $key ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<input type="text" name="name" class="form-control form-control-lg border-0 bg-light rounded-3" placeholder="e.g. Supervisor" required>
|
||||
<p class="form-text small text-muted mt-2">After creating the group, you will be redirected to define its specific permissions.</p>
|
||||
</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="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Save Group</button>
|
||||
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-toggle="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Create & Configure</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
|
||||
288
admin/users.php
288
admin/users.php
@ -3,219 +3,131 @@ require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
$pdo = db();
|
||||
require_permission('all'); // Only super admin can manage users
|
||||
require_permission('users_view');
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Add User
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_user') {
|
||||
$username = $_POST['username'];
|
||||
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||
$full_name = $_POST['full_name'];
|
||||
$email = $_POST['email'];
|
||||
$group_id = $_POST['group_id'];
|
||||
$assigned_outlets = $_POST['outlets'] ?? [];
|
||||
|
||||
// Check if user exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
if ($stmt->fetch()) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Username already exists.</div>';
|
||||
} else {
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, password, full_name, email, group_id) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $password, $full_name, $email, $group_id]);
|
||||
$user_id = $pdo->lastInsertId();
|
||||
|
||||
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([$user_id, $outlet_id]);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>User added successfully!</div>';
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding user: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Delete
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = $_GET['delete'];
|
||||
$currentUser = get_logged_user();
|
||||
// Prevent deleting yourself
|
||||
if ($id != $currentUser['id']) {
|
||||
if (!has_permission('users_del')) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3">Access Denied: You do not have permission to delete users.</div>';
|
||||
} else {
|
||||
$id = $_GET['delete'];
|
||||
$pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]);
|
||||
header("Location: users.php");
|
||||
exit;
|
||||
}
|
||||
header("Location: users.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Fetch users with group names and assigned outlets
|
||||
$users_sql = "
|
||||
SELECT u.*, g.name as group_name, GROUP_CONCAT(o.name SEPARATOR ', ') as assigned_outlets
|
||||
FROM users u
|
||||
LEFT JOIN user_groups g ON u.group_id = g.id
|
||||
LEFT JOIN user_outlets uo ON u.id = uo.user_id
|
||||
LEFT JOIN outlets o ON uo.outlet_id = o.id
|
||||
GROUP BY u.id
|
||||
ORDER BY u.id ASC
|
||||
";
|
||||
$users = $pdo->query($users_sql)->fetchAll();
|
||||
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY name")->fetchAll();
|
||||
$all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
||||
// Handle Search
|
||||
$search = $_GET['search'] ?? '';
|
||||
$params = [];
|
||||
$query = "SELECT u.*, g.name as group_name
|
||||
FROM users u
|
||||
LEFT JOIN user_groups g ON u.group_id = g.id";
|
||||
|
||||
if ($search) {
|
||||
$query .= " WHERE u.username LIKE ? OR u.full_name LIKE ? OR u.email LIKE ?";
|
||||
$params = ["%$search%", "%search%", "%search%"];
|
||||
}
|
||||
|
||||
$query .= " ORDER BY u.id DESC";
|
||||
$users_pagination = paginate_query($pdo, $query, $params);
|
||||
$users = $users_pagination['data'];
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1 text-dark">User Management</h2>
|
||||
<p class="text-muted mb-0">Manage system users and their access levels</p>
|
||||
<h2 class="fw-bold mb-1">Users</h2>
|
||||
<p class="text-muted mb-0">Manage system staff and access</p>
|
||||
</div>
|
||||
<button class="btn btn-primary shadow-sm rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="bi bi-person-plus-fill me-1"></i> Add User
|
||||
</button>
|
||||
<?php if (has_permission('users_add')): ?>
|
||||
<a href="user_edit.php" class="btn btn-primary btn-lg shadow-sm" style="border-radius: 10px;">
|
||||
<i class="bi bi-person-plus me-1"></i> Add User
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-3 overflow-hidden">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">User</th>
|
||||
<th>Email</th>
|
||||
<th>Role / Group</th>
|
||||
<th>Outlets</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-flex align-items-center justify-content-center me-3 shadow-sm" style="width:42px;height:42px; font-weight:600;">
|
||||
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark"><?= htmlspecialchars($user['full_name']) ?></div>
|
||||
<div class="text-muted small">@<?= htmlspecialchars($user['username']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($user['email']) ?></td>
|
||||
<td>
|
||||
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 rounded-pill px-3" style="font-weight:600;">
|
||||
<?= htmlspecialchars($user['group_name'] ?? 'No Group') ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small text-muted" style="max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="<?= htmlspecialchars($user['assigned_outlets'] ?: 'None') ?>">
|
||||
<?= htmlspecialchars($user['assigned_outlets'] ?: 'None') ?>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($user['is_active']): ?>
|
||||
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 rounded-pill px-3">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger border-opacity-25 rounded-pill px-3">Inactive</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="d-inline-flex gap-2">
|
||||
<a href="user_edit.php?id=<?= $user['id'] ?>" class="btn btn-sm btn-icon-soft edit" title="Edit User">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</a>
|
||||
<?php if ($user['id'] != get_logged_user()['id']): ?>
|
||||
<a href="?delete=<?= $user['id'] ?>" class="btn btn-sm btn-icon-soft delete" onclick="return confirm('Delete this user?')" title="Delete">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0 px-4 pt-4">
|
||||
<h5 class="modal-title fw-bold">Add New User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body p-4">
|
||||
<input type="hidden" name="action" value="add_user">
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold text-muted">FULL NAME</label>
|
||||
<input type="text" name="full_name" class="form-control border-0 bg-light" placeholder="e.g. John Doe" required style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold text-muted">EMAIL</label>
|
||||
<input type="email" name="email" class="form-control border-0 bg-light" placeholder="e.g. john@example.com" required style="border-radius: 12px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-muted">USERNAME</label>
|
||||
<input type="text" name="username" class="form-control border-0 bg-light" placeholder="e.g. jdoe" required style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-muted">PASSWORD</label>
|
||||
<input type="password" name="password" class="form-control border-0 bg-light" placeholder="******" required style="border-radius: 12px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label>
|
||||
<select name="group_id" class="form-select border-0 bg-light" required style="border-radius: 12px;">
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-muted d-block mb-2">ASSIGNED OUTLETS</label>
|
||||
<div class="p-3 bg-light rounded-4">
|
||||
<div class="row">
|
||||
<?php foreach ($all_outlets as $outlet): ?>
|
||||
<div class="col-md-6 mb-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="outlets[]" value="<?= $outlet['id'] ?>" id="new_outlet_<?= $outlet['id'] ?>">
|
||||
<label class="form-check-label small" for="new_outlet_<?= $outlet['id'] ?>">
|
||||
<?= htmlspecialchars($outlet['name']) ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="p-3 border-bottom bg-light bg-opacity-50">
|
||||
<form method="GET" class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<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 users..." value="<?= htmlspecialchars($search) ?>">
|
||||
</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="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Create User</button>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-light border">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">User</th>
|
||||
<th>Role / Group</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Joined</th>
|
||||
<th class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-primary bg-opacity-10 text-primary d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; font-weight: 600;">
|
||||
<?= strtoupper(substr($user['username'], 0, 1)) ?>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark"><?= htmlspecialchars($user['full_name'] ?: $user['username']) ?></div>
|
||||
<div class="text-muted small">@<?= htmlspecialchars($user['username']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 px-3 rounded-pill fw-medium">
|
||||
<?= htmlspecialchars($user['group_name'] ?: 'No Group') ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($user['email'] ?: '-') ?></td>
|
||||
<td>
|
||||
<?php if ($user['is_active']): ?>
|
||||
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 px-3 rounded-pill">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger border-opacity-25 px-3 rounded-pill">Inactive</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-muted small"><?= date('M d, Y', strtotime($user['created_at'])) ?></td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="d-inline-flex gap-2">
|
||||
<?php if (has_permission('users_add')): ?>
|
||||
<a href="user_edit.php?id=<?= $user['id'] ?>" class="btn btn-sm btn-outline-primary rounded-pill px-3">Edit</a>
|
||||
<?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('Delete this user?')">Delete</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="p-3 border-top">
|
||||
<?php render_pagination_controls($users_pagination); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
153
api/order.php
153
api/order.php
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/WablasService.php';
|
||||
|
||||
$input = file_get_contents('php://input');
|
||||
@ -70,8 +71,9 @@ try {
|
||||
$customer_phone = $data['customer_phone'] ?? null;
|
||||
|
||||
// Fetch Loyalty Settings
|
||||
$settingsStmt = $pdo->query("SELECT points_per_order, points_for_free_meal FROM loyalty_settings WHERE id = 1");
|
||||
$settingsStmt = $pdo->query("SELECT is_enabled, points_per_order, points_for_free_meal FROM loyalty_settings WHERE id = 1");
|
||||
$settings = $settingsStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$loyalty_enabled = $settings ? (bool)$settings['is_enabled'] : true;
|
||||
$points_per_order = $settings ? intval($settings['points_per_order']) : 10;
|
||||
$points_threshold = $settings ? intval($settings['points_for_free_meal']) : 70;
|
||||
|
||||
@ -93,7 +95,7 @@ try {
|
||||
}
|
||||
|
||||
// Loyalty Redemption Logic
|
||||
$redeem_loyalty = !empty($data['redeem_loyalty']);
|
||||
$redeem_loyalty = !empty($data['redeem_loyalty']) && $loyalty_enabled;
|
||||
if ($redeem_loyalty && $customer_id) {
|
||||
if ($current_points < $points_threshold) {
|
||||
throw new Exception("Insufficient loyalty points for redemption.");
|
||||
@ -113,37 +115,75 @@ try {
|
||||
}
|
||||
|
||||
// Award Points
|
||||
if ($customer_id) {
|
||||
if ($customer_id && $loyalty_enabled) {
|
||||
$awardStmt = $pdo->prepare("UPDATE customers SET points = points + ? WHERE id = ?");
|
||||
$awardStmt->execute([$points_per_order, $customer_id]);
|
||||
$points_awarded = $points_per_order;
|
||||
}
|
||||
|
||||
// Payment Type
|
||||
// User/Payment info
|
||||
$user = get_logged_user();
|
||||
$user_id = $user ? $user['id'] : null;
|
||||
|
||||
$payment_type_id = isset($data['payment_type_id']) ? intval($data['payment_type_id']) : null;
|
||||
|
||||
$discount = isset($data['discount']) ? floatval($data['discount']) : 0.00;
|
||||
$total_amount = isset($data['total_amount']) ? floatval($data['total_amount']) : 0.00;
|
||||
|
||||
// Total amount will be recalculated on server to be safe
|
||||
$calculated_total = 0;
|
||||
|
||||
// First, process items to calculate real total
|
||||
$processed_items = [];
|
||||
if (!empty($data['items']) && is_array($data['items'])) {
|
||||
foreach ($data['items'] as $item) {
|
||||
$pid = $item['product_id'] ?? ($item['id'] ?? null);
|
||||
if (!$pid) continue;
|
||||
|
||||
$qty = intval($item['quantity'] ?? 1);
|
||||
$vid = $item['variant_id'] ?? null;
|
||||
|
||||
// Fetch Product Price (Promo aware)
|
||||
$pStmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
|
||||
$pStmt->execute([$pid]);
|
||||
$product = $pStmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$product) continue;
|
||||
|
||||
$unit_price = get_product_price($product);
|
||||
|
||||
// Add variant adjustment
|
||||
if ($vid) {
|
||||
$vStmt = $pdo->prepare("SELECT price_adjustment FROM product_variants WHERE id = ? AND product_id = ?");
|
||||
$vStmt->execute([$vid, $pid]);
|
||||
$vAdjustment = $vStmt->fetchColumn();
|
||||
if ($vAdjustment !== false) {
|
||||
$unit_price += floatval($vAdjustment);
|
||||
}
|
||||
}
|
||||
|
||||
$item_total = $unit_price * $qty;
|
||||
$calculated_total += $item_total;
|
||||
|
||||
$processed_items[] = [
|
||||
'product_id' => $pid,
|
||||
'variant_id' => $vid,
|
||||
'quantity' => $qty,
|
||||
'unit_price' => $unit_price,
|
||||
'name' => $product['name']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$final_total = max(0, $calculated_total - $discount);
|
||||
|
||||
// Check for Existing Order ID (Update Mode)
|
||||
$order_id = isset($data['order_id']) ? intval($data['order_id']) : null;
|
||||
$is_update = false;
|
||||
|
||||
if ($order_id) {
|
||||
// Verify existence and status (optional: only update pending orders?)
|
||||
$checkStmt = $pdo->prepare("SELECT id FROM orders WHERE id = ?");
|
||||
$checkStmt->execute([$order_id]);
|
||||
if ($checkStmt->fetch()) {
|
||||
$is_update = true;
|
||||
} else {
|
||||
// If ID sent but not found, create new? Or error?
|
||||
// Let's treat as new if not found to avoid errors, or maybe user meant to update.
|
||||
// Safe bet: error if explicit update requested but failed.
|
||||
// But for now, let's just create new if ID is invalid, or better, stick to the plan: Update if ID present.
|
||||
$order_id = null; // Reset to create new
|
||||
$order_id = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,81 +197,52 @@ try {
|
||||
$stmt->execute([
|
||||
$outlet_id, $table_id, $table_number, $order_type,
|
||||
$customer_id, $customer_name, $customer_phone,
|
||||
$payment_type_id, $total_amount, $discount, $user_id,
|
||||
$payment_type_id, $final_total, $discount, $user_id,
|
||||
$order_id
|
||||
]);
|
||||
|
||||
// Clear existing items to replace them
|
||||
// Clear existing items
|
||||
$delStmt = $pdo->prepare("DELETE FROM order_items WHERE order_id = ?");
|
||||
$delStmt->execute([$order_id]);
|
||||
|
||||
} else {
|
||||
// INSERT New Order
|
||||
$stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_id, table_number, order_type, customer_id, customer_name, customer_phone, payment_type_id, total_amount, discount, user_id, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
|
||||
$stmt->execute([$outlet_id, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $payment_type_id, $total_amount, $discount, $user_id]);
|
||||
$stmt->execute([$outlet_id, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $payment_type_id, $final_total, $discount, $user_id]);
|
||||
$order_id = $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
// Insert Items (Common for both Insert and Update)
|
||||
// Insert Items
|
||||
$item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, variant_id, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
||||
|
||||
// Pre-prepare statements for name fetching
|
||||
$prodNameStmt = $pdo->prepare("SELECT name FROM products WHERE id = ?");
|
||||
$varNameStmt = $pdo->prepare("SELECT name FROM product_variants WHERE id = ?");
|
||||
|
||||
$order_items_list = []; // To store item details for notification
|
||||
$order_items_list = [];
|
||||
|
||||
if (!empty($data['items']) && is_array($data['items'])) {
|
||||
foreach ($data['items'] as $item) {
|
||||
$pid = $item['product_id'] ?? ($item['id'] ?? null);
|
||||
$qty = $item['quantity'] ?? 1;
|
||||
$price = $item['unit_price'] ?? ($item['price'] ?? 0);
|
||||
$vid = $item['variant_id'] ?? null;
|
||||
foreach ($processed_items as $pi) {
|
||||
$item_stmt->execute([$order_id, $pi['product_id'], $pi['variant_id'], $pi['quantity'], $pi['unit_price']]);
|
||||
|
||||
if ($pid) {
|
||||
$item_stmt->execute([$order_id, $pid, $vid, $qty, $price]);
|
||||
|
||||
// Fetch names for notification
|
||||
$prodNameStmt->execute([$pid]);
|
||||
$pRow = $prodNameStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$pName = $pRow ? $pRow['name'] : 'Item';
|
||||
|
||||
if ($vid) {
|
||||
$varNameStmt->execute([$vid]);
|
||||
$vRow = $varNameStmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($vRow) {
|
||||
$pName .= " (" . $vRow['name'] . ")";
|
||||
}
|
||||
}
|
||||
$order_items_list[] = "$qty x $pName";
|
||||
}
|
||||
$pName = $pi['name'];
|
||||
if ($pi['variant_id']) {
|
||||
$varNameStmt->execute([$pi['variant_id']]);
|
||||
$vName = $varNameStmt->fetchColumn();
|
||||
if ($vName) $pName .= " ($vName)";
|
||||
}
|
||||
$order_items_list[] = "{$pi['quantity']} x $pName";
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
// --- Post-Transaction Actions ---
|
||||
|
||||
// Send WhatsApp Notification if Customer exists
|
||||
// --- Post-Transaction Actions (WhatsApp) ---
|
||||
if ($customer_id && $customer_phone) {
|
||||
try {
|
||||
// Calculate final points
|
||||
$final_points = $current_points - $points_deducted + $points_awarded;
|
||||
|
||||
// Determine message content
|
||||
$final_points = $current_points - $points_deducted + ($loyalty_enabled ? $points_awarded : 0);
|
||||
$wablas = new WablasService($pdo);
|
||||
|
||||
// Fetch Company Name
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
$companySettings = get_company_settings();
|
||||
$company_name = $companySettings['company_name'] ?? 'Flatlogic POS';
|
||||
|
||||
// Fetch Template
|
||||
$stmt = $pdo->prepare("SELECT setting_value FROM integration_settings WHERE provider = 'wablas' AND setting_key = 'order_template'");
|
||||
$stmt->execute();
|
||||
$template = $stmt->fetchColumn();
|
||||
|
||||
// Default Template if not set
|
||||
if (!$template) {
|
||||
$template = "Dear *{customer_name}*,
|
||||
|
||||
@ -248,35 +259,27 @@ You've earned *{points_earned} points* with this order.
|
||||
{loyalty_status}";
|
||||
}
|
||||
|
||||
// Calculate Loyalty Status String
|
||||
$loyalty_status = "";
|
||||
if ($final_points >= $points_threshold) {
|
||||
$loyalty_status = "🎉 Congratulations! You have enough points for a *FREE MEAL* on your next visit!";
|
||||
} else {
|
||||
$needed = $points_threshold - $final_points;
|
||||
$loyalty_status = "You need *$needed more points* to unlock a free meal.";
|
||||
if ($loyalty_enabled) {
|
||||
$loyalty_status = ($final_points >= $points_threshold)
|
||||
? "🎉 Congratulations! You have enough points for a *FREE MEAL* on your next visit!"
|
||||
: "You need *" . ($points_threshold - $final_points) . " more points* to unlock a free meal.";
|
||||
}
|
||||
|
||||
// Prepare replacements
|
||||
$replacements = [
|
||||
'{customer_name}' => $customer_name,
|
||||
'{company_name}' => $company_name,
|
||||
'{order_id}' => $order_id,
|
||||
'{order_details}' => !empty($order_items_list) ? implode("\n", $order_items_list) : 'N/A',
|
||||
'{total_amount}' => number_format($total_amount, 3),
|
||||
'{points_earned}' => $points_awarded,
|
||||
'{order_details}' => implode("\n", $order_items_list),
|
||||
'{total_amount}' => number_format($final_total, 3),
|
||||
'{points_earned}' => $loyalty_enabled ? $points_awarded : 0,
|
||||
'{points_redeemed}' => $points_deducted,
|
||||
'{new_balance}' => $final_points,
|
||||
'{loyalty_status}' => $loyalty_status
|
||||
];
|
||||
|
||||
$msg = str_replace(array_keys($replacements), array_values($replacements), $template);
|
||||
|
||||
// Send (fire and forget, don't block response on failure, just log)
|
||||
$res = $wablas->sendMessage($customer_phone, $msg);
|
||||
if (!$res['success']) {
|
||||
error_log("Wablas Notification Failed: " . $res['message']);
|
||||
}
|
||||
$wablas->sendMessage($customer_phone, $msg);
|
||||
|
||||
} catch (Exception $w) {
|
||||
error_log("Wablas Exception: " . $w->getMessage());
|
||||
@ -286,9 +289,7 @@ You've earned *{points_earned} points* with this order.
|
||||
echo json_encode(['success' => true, 'order_id' => $order_id]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log("Order Error: " . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
@ -256,7 +256,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// customerInfo.classList.remove('d-none');
|
||||
|
||||
// Loyalty Logic
|
||||
if (loyaltySection) {
|
||||
if (loyaltySection && typeof LOYALTY_SETTINGS !== 'undefined' && LOYALTY_SETTINGS.is_enabled) {
|
||||
loyaltySection.classList.remove('d-none');
|
||||
loyaltyPointsDisplay.textContent = cust.points + ' pts';
|
||||
|
||||
@ -296,6 +296,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Loyalty Redeem Click
|
||||
if (redeemLoyaltyBtn) {
|
||||
redeemLoyaltyBtn.addEventListener('click', () => {
|
||||
if (typeof LOYALTY_SETTINGS === 'undefined' || !LOYALTY_SETTINGS.is_enabled) return;
|
||||
|
||||
if (cart.length === 0) {
|
||||
showToast("Cart is empty!", "warning");
|
||||
return;
|
||||
|
||||
18
db/migrations/012_expenses_schema.sql
Normal file
18
db/migrations/012_expenses_schema.sql
Normal file
@ -0,0 +1,18 @@
|
||||
CREATE TABLE IF NOT EXISTS expense_categories (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS expenses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
category_id INT NOT NULL,
|
||||
outlet_id INT NOT NULL,
|
||||
amount DECIMAL(10, 2) NOT NULL,
|
||||
description TEXT,
|
||||
expense_date DATE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (category_id) REFERENCES expense_categories(id),
|
||||
FOREIGN KEY (outlet_id) REFERENCES outlets(id)
|
||||
);
|
||||
4
db/migrations/013_product_promotions.sql
Normal file
4
db/migrations/013_product_promotions.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE products
|
||||
ADD COLUMN promo_discount_percent DECIMAL(5, 2) DEFAULT NULL,
|
||||
ADD COLUMN promo_date_from DATE DEFAULT NULL,
|
||||
ADD COLUMN promo_date_to DATE DEFAULT NULL;
|
||||
18
db/migrations/014_loyalty_toggle.sql
Normal file
18
db/migrations/014_loyalty_toggle.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- Add is_enabled column to loyalty_settings table
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = "loyalty_settings";
|
||||
SET @columnname = "is_enabled";
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(table_name = @tablename)
|
||||
AND (table_schema = @dbname)
|
||||
AND (column_name = @columnname)
|
||||
) > 0,
|
||||
"SELECT 1",
|
||||
"ALTER TABLE loyalty_settings ADD COLUMN is_enabled TINYINT(1) DEFAULT 1;"
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
@ -3,10 +3,12 @@
|
||||
function get_company_settings() {
|
||||
static $settings = null;
|
||||
if ($settings === null) {
|
||||
$pdo = db();
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT * FROM company_settings LIMIT 1");
|
||||
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (function_exists('db')) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM company_settings LIMIT 1");
|
||||
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Log error or ignore if table doesn't exist yet
|
||||
}
|
||||
@ -33,6 +35,31 @@ function format_currency($amount) {
|
||||
return $settings['currency_symbol'] . number_format((float)$amount, (int)$settings['currency_decimals']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the current price of a product considering promotions.
|
||||
*
|
||||
* @param array|object $product The product data from DB.
|
||||
* @return float The effective price.
|
||||
*/
|
||||
function get_product_price($product) {
|
||||
$product = (array)$product;
|
||||
$price = (float)$product['price'];
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$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'];
|
||||
|
||||
if ($promo_active) {
|
||||
$discount = (float)$product['promo_discount_percent'];
|
||||
$price = $price * (1 - ($discount / 100));
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate a query result.
|
||||
*
|
||||
@ -247,6 +274,7 @@ function has_permission($permission) {
|
||||
if (!$user) return false;
|
||||
|
||||
$userPermissions = $user['permissions'] ?? '';
|
||||
// Global bypass for super admins
|
||||
if ($userPermissions === 'all') return true;
|
||||
|
||||
$perms = explode(',', $userPermissions);
|
||||
|
||||
19
kitchen.php
19
kitchen.php
@ -2,7 +2,7 @@
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/functions.php';
|
||||
|
||||
require_login();
|
||||
require_permission('kitchen_view');
|
||||
|
||||
$pdo = db();
|
||||
$currentUser = get_logged_user();
|
||||
@ -101,17 +101,21 @@ if (!has_permission('all')) {
|
||||
</select>
|
||||
<span id="clock" class="text-muted d-none d-md-inline"></span>
|
||||
|
||||
<?php if (has_permission('kitchen_add')): ?>
|
||||
<button class="btn btn-danger btn-sm" onclick="serveAll()">
|
||||
<i class="bi bi-check2-all"></i> Serve All
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-person-circle"></i> <?= htmlspecialchars($currentUser['username']) ?>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0">
|
||||
<?php if (has_permission('dashboard_view')): ?>
|
||||
<li><a class="dropdown-item" href="admin/"><i class="bi bi-shield-lock me-2"></i> Admin Panel</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<?php endif; ?>
|
||||
<li><a class="dropdown-item text-danger" href="logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -130,6 +134,7 @@ if (!has_permission('all')) {
|
||||
|
||||
<script>
|
||||
const OUTLET_ID = <?= $current_outlet_id ?>;
|
||||
const CAN_EDIT = <?= has_permission('kitchen_add') ? 'true' : 'false' ?>;
|
||||
|
||||
async function fetchOrders() {
|
||||
try {
|
||||
@ -150,8 +155,6 @@ function renderOrders(orders) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the HTML using a variable, so we don't clear/flash the grid if it's identical
|
||||
// But for now, simple innerHTML replacement is fine for this MVP.
|
||||
const newHtml = orders.map(order => {
|
||||
let actionBtn = '';
|
||||
let statusBadge = '';
|
||||
@ -160,15 +163,15 @@ function renderOrders(orders) {
|
||||
if (order.status === 'pending') {
|
||||
statusBadge = '<span class="badge badge-pending">Pending</span>';
|
||||
cardClass += 'status-pending';
|
||||
actionBtn = `<button class="btn btn-info btn-sm w-100 text-white" onclick="updateStatus(${order.id}, 'preparing')">Start Preparing</button>`;
|
||||
if (CAN_EDIT) actionBtn = `<button class="btn btn-info btn-sm w-100 text-white" onclick="updateStatus(${order.id}, 'preparing')">Start Preparing</button>`;
|
||||
} else if (order.status === 'preparing') {
|
||||
statusBadge = '<span class="badge badge-preparing">Preparing</span>';
|
||||
cardClass += 'status-preparing';
|
||||
actionBtn = `<button class="btn btn-success btn-sm w-100" onclick="updateStatus(${order.id}, 'ready')">Mark Ready</button>`;
|
||||
if (CAN_EDIT) actionBtn = `<button class="btn btn-success btn-sm w-100" onclick="updateStatus(${order.id}, 'ready')">Mark Ready</button>`;
|
||||
} else if (order.status === 'ready') {
|
||||
statusBadge = '<span class="badge badge-ready">Ready</span>';
|
||||
cardClass += 'status-ready';
|
||||
actionBtn = `<button class="btn btn-secondary btn-sm w-100" onclick="updateStatus(${order.id}, 'completed')">Serve / Complete</button>`;
|
||||
if (CAN_EDIT) actionBtn = `<button class="btn btn-secondary btn-sm w-100" onclick="updateStatus(${order.id}, 'completed')">Serve / Complete</button>`;
|
||||
}
|
||||
|
||||
const itemsHtml = order.items.map(item => `
|
||||
@ -198,7 +201,7 @@ function renderOrders(orders) {
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-top-0 pb-3">
|
||||
${actionBtn}
|
||||
${actionBtn || '<div class="text-center small text-muted">View Only</div>'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -209,6 +212,7 @@ function renderOrders(orders) {
|
||||
}
|
||||
|
||||
function updateStatus(orderId, newStatus) {
|
||||
if (!CAN_EDIT) return;
|
||||
Swal.fire({
|
||||
title: 'Update Status?',
|
||||
text: `Move order #${orderId} to ${newStatus}?`,
|
||||
@ -251,6 +255,7 @@ async function performUpdate(orderId, newStatus) {
|
||||
}
|
||||
|
||||
async function serveAll() {
|
||||
if (!CAN_EDIT) return;
|
||||
const result = await Swal.fire({
|
||||
title: 'Serve All Orders?',
|
||||
text: "This will mark all active orders as completed and clear the screen.",
|
||||
|
||||
46
pos.php
46
pos.php
@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/functions.php';
|
||||
|
||||
require_login();
|
||||
require_permission('pos_view');
|
||||
|
||||
$pdo = db();
|
||||
$currentUser = get_logged_user();
|
||||
@ -60,6 +60,13 @@ foreach ($outlets as $o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Loyalty Settings
|
||||
$loyalty_stmt = $pdo->query("SELECT * FROM loyalty_settings WHERE id = 1");
|
||||
$loyalty_settings = $loyalty_stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$loyalty_settings) {
|
||||
$loyalty_settings = ['is_enabled' => 0, 'points_per_order' => 0, 'points_for_free_meal' => 0];
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@ -104,10 +111,19 @@ foreach ($outlets as $o) {
|
||||
</a>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<?php if (has_permission('kitchen_view')): ?>
|
||||
<a href="kitchen.php" class="btn btn-sm btn-outline-secondary">Kitchen View</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (has_permission('pos_add')): ?>
|
||||
<button class="btn btn-sm btn-outline-danger" id="recall-bill-btn"><i class="bi bi-clock-history"></i> Recall Bill</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="pos.php?order_type=dine-in" class="btn btn-sm btn-outline-secondary">Waiter View</a>
|
||||
|
||||
<?php if (has_permission('orders_view')): ?>
|
||||
<a href="admin/orders.php" class="btn btn-sm btn-outline-secondary">Current Orders</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="current-table-display" class="badge bg-light text-dark border px-3 py-2" style="display: none; font-size: 0.9rem;">
|
||||
Table <?= htmlspecialchars($table_id) ?>
|
||||
@ -186,18 +202,30 @@ foreach ($outlets as $o) {
|
||||
<div class="row row-cols-2 row-cols-lg-5 g-3" id="products-container">
|
||||
<?php foreach ($all_products as $product):
|
||||
$has_variants = !empty($variants_by_product[$product['id']]);
|
||||
$effective_price = get_product_price($product);
|
||||
$is_promo = $effective_price < $product['price'];
|
||||
?>
|
||||
<div class="col product-item" data-category-id="<?= $product['category_id'] ?>">
|
||||
<div class="card h-100 border-0 shadow-sm product-card add-to-cart"
|
||||
data-id="<?= $product['id'] ?>"
|
||||
data-name="<?= htmlspecialchars($product['name']) ?>"
|
||||
data-price="<?= $product['price'] ?>"
|
||||
data-price="<?= $effective_price ?>"
|
||||
data-has-variants="<?= $has_variants ? 'true' : 'false' ?>">
|
||||
<div class="position-relative">
|
||||
<img src="https://picsum.photos/seed/<?= $product['id'] ?>/300/200" class="card-img-top object-fit-cover" alt="..." style="height: 120px;">
|
||||
<div class="position-absolute bottom-0 end-0 m-2">
|
||||
<span class="badge bg-dark rounded-pill"><?= format_currency($product['price']) ?></span>
|
||||
<?php if ($is_promo): ?>
|
||||
<span class="badge bg-danger rounded-pill shadow-sm"><?= format_currency($effective_price) ?></span>
|
||||
<span class="badge bg-dark rounded-pill opacity-75 text-decoration-line-through x-small" style="font-size: 0.7rem;"><?= format_currency($product['price']) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-dark rounded-pill"><?= format_currency($product['price']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($is_promo): ?>
|
||||
<div class="position-absolute top-0 start-0 m-2">
|
||||
<span class="badge bg-warning text-dark fw-bold rounded-pill">SALE</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<h6 class="card-title fw-bold small mb-1 text-truncate"><?= htmlspecialchars($product['name']) ?></h6>
|
||||
@ -246,7 +274,8 @@ foreach ($outlets as $o) {
|
||||
<i class="bi bi-check-circle-fill me-1"></i> <span id="customer-name-display"></span>
|
||||
</div>
|
||||
|
||||
<!-- Loyalty Section -->
|
||||
<!-- Loyalty Section (Hidden if disabled) -->
|
||||
<?php if ($loyalty_settings['is_enabled']): ?>
|
||||
<div id="loyalty-section" class="d-none mt-2 p-2 bg-warning-subtle rounded border border-warning">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
@ -259,6 +288,7 @@ foreach ($outlets as $o) {
|
||||
</div>
|
||||
<div id="loyalty-message" class="small text-muted mt-1 fst-italic" style="font-size: 0.75rem;"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -290,6 +320,8 @@ foreach ($outlets as $o) {
|
||||
<span class="fs-5 fw-bold">Total</span>
|
||||
<span class="fs-4 fw-bold text-primary" id="cart-total-price"><?= format_currency(0) ?></span>
|
||||
</div>
|
||||
|
||||
<?php if (has_permission('pos_add')): ?>
|
||||
<div class="d-flex gap-2 w-100">
|
||||
<button class="btn btn-primary w-50 btn-lg shadow-sm" id="quick-order-btn" disabled>
|
||||
Quick Order <i class="bi bi-lightning-fill ms-2"></i>
|
||||
@ -298,6 +330,11 @@ foreach ($outlets as $o) {
|
||||
Place Order <i class="bi bi-clock ms-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-warning small py-2 mb-0 text-center">
|
||||
<i class="bi bi-info-circle me-1"></i> View Only Mode
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -402,6 +439,7 @@ foreach ($outlets as $o) {
|
||||
|
||||
<script>
|
||||
const COMPANY_SETTINGS = <?= json_encode($settings) ?>;
|
||||
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
|
||||
const PRODUCT_VARIANTS = <?= json_encode($variants_by_product) ?>;
|
||||
const PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
|
||||
const CURRENT_OUTLET = <?= json_encode(['id' => $outlet_id, 'name' => $current_outlet_name]) ?>;
|
||||
|
||||
19
qorder.php
19
qorder.php
@ -90,18 +90,33 @@ foreach ($variants_raw as $v) {
|
||||
<div class="row g-3" id="products-container">
|
||||
<?php foreach ($all_products as $product):
|
||||
$has_variants = !empty($variants_by_product[$product['id']]);
|
||||
$effective_price = get_product_price($product);
|
||||
$is_promo = $effective_price < (float)$product['price'];
|
||||
?>
|
||||
<div class="col-6 col-md-4 product-item" data-category-id="<?= $product['category_id'] ?>">
|
||||
<div class="card h-100 product-card"
|
||||
onclick="handleProductClick(<?= htmlspecialchars(json_encode([
|
||||
'id' => $product['id'],
|
||||
'name' => $product['name'],
|
||||
'price' => (float)$product['price'],
|
||||
'price' => (float)$effective_price,
|
||||
'has_variants' => $has_variants
|
||||
])) ?>)">
|
||||
<div class="position-relative">
|
||||
<img src="https://picsum.photos/seed/<?= $product['id'] ?>/400/300" class="card-img-top product-img" alt="<?= htmlspecialchars($product['name']) ?>">
|
||||
<div class="badge-price text-primary"><?= number_format((float)$product['price'], 3) ?> OMR</div>
|
||||
<div class="badge-price text-primary">
|
||||
<?php if ($is_promo): ?>
|
||||
<span class="text-danger fw-bold"><?= number_format($effective_price, 3) ?></span>
|
||||
<small class="text-muted text-decoration-line-through ms-1" style="font-size: 0.7rem;"><?= number_format((float)$product['price'], 3) ?></small>
|
||||
<?php else: ?>
|
||||
<?= number_format((float)$product['price'], 3) ?>
|
||||
<?php endif; ?>
|
||||
OMR
|
||||
</div>
|
||||
<?php if ($is_promo): ?>
|
||||
<div class="position-absolute top-0 start-0 m-2">
|
||||
<span class="badge bg-warning text-dark fw-bold rounded-pill" style="font-size: 0.6rem;">SALE</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<h6 class="card-title mb-1 small fw-bold text-truncate"><?= htmlspecialchars($product['name']) ?></h6>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user