351 lines
17 KiB
PHP
351 lines
17 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/../db/config.php';
|
|
require_once __DIR__ . '/../includes/functions.php';
|
|
|
|
$pdo = db();
|
|
require_permission('orders_view');
|
|
|
|
// Handle status updates
|
|
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 = ?");
|
|
$stmt->execute([$new_status, $order_id]);
|
|
header("Location: orders.php?" . http_build_query($_GET)); // Keep filters
|
|
exit;
|
|
}
|
|
|
|
// Handle stopping all promotions
|
|
if (isset($_POST['action']) && $_POST['action'] === 'stop_promotions') {
|
|
if (!has_permission('manage_products')) {
|
|
header("Location: orders.php?error=permission_denied");
|
|
exit;
|
|
}
|
|
// Set promo_date_to to yesterday for all currently active promotions
|
|
$stmt = $pdo->prepare("UPDATE products SET promo_date_to = DATE_SUB(CURDATE(), INTERVAL 1 DAY) WHERE (promo_date_to >= CURDATE() OR promo_date_to IS NULL) AND promo_discount_percent IS NOT NULL");
|
|
$stmt->execute();
|
|
header("Location: orders.php?success=promotions_stopped");
|
|
exit;
|
|
}
|
|
|
|
// Fetch Outlets for Filter
|
|
$outlets = $pdo->query("SELECT id, name FROM outlets ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Build Query with Filters
|
|
$params = [];
|
|
$where = [];
|
|
|
|
// Filter: Outlet
|
|
if (!empty($_GET['outlet_id'])) {
|
|
$where[] = "o.outlet_id = :outlet_id";
|
|
$params[':outlet_id'] = $_GET['outlet_id'];
|
|
}
|
|
|
|
// Filter: Date Range
|
|
if (!empty($_GET['start_date'])) {
|
|
$where[] = "DATE(o.created_at) >= :start_date";
|
|
$params[':start_date'] = $_GET['start_date'];
|
|
}
|
|
if (!empty($_GET['end_date'])) {
|
|
$where[] = "DATE(o.created_at) <= :end_date";
|
|
$params[':end_date'] = $_GET['end_date'];
|
|
}
|
|
|
|
// Filter: Search (Order No)
|
|
if (!empty($_GET['search'])) {
|
|
if (is_numeric($_GET['search'])) {
|
|
$where[] = "o.id = :search";
|
|
$params[':search'] = $_GET['search'];
|
|
}
|
|
}
|
|
|
|
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
|
|
|
// Calculate Total Sum for filtered orders
|
|
$sum_query = "SELECT SUM(total_amount) as total_sum FROM orders o $where_clause";
|
|
$stmt_sum = $pdo->prepare($sum_query);
|
|
$stmt_sum->execute($params);
|
|
$total_sum = $stmt_sum->fetchColumn() ?: 0;
|
|
|
|
// Main Query
|
|
$query = "SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
|
|
(SELECT GROUP_CONCAT(CONCAT(p.name, ' x', oi.quantity) SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items_summary
|
|
FROM orders o
|
|
LEFT JOIN outlets ot ON o.outlet_id = ot.id
|
|
LEFT JOIN payment_types pt ON o.payment_type_id = pt.id
|
|
$where_clause
|
|
ORDER BY o.created_at DESC";
|
|
|
|
$orders_pagination = paginate_query($pdo, $query, $params);
|
|
$orders = $orders_pagination['data'];
|
|
|
|
// Add total sum to pagination object for rendering
|
|
$orders_pagination['total_amount_sum'] = $total_sum;
|
|
|
|
include 'includes/header.php';
|
|
?>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="fw-bold mb-0">Order Management</h2>
|
|
<div class="d-flex gap-2">
|
|
<?php if (has_permission('manage_products')): ?>
|
|
<form method="POST" onsubmit="return confirm('Are you sure you want to stop all running promotions? This will end all active promotions by setting their end date to yesterday.');">
|
|
<input type="hidden" name="action" value="stop_promotions">
|
|
<button type="submit" class="btn btn-danger shadow-sm">
|
|
<i class="bi bi-stop-circle me-1"></i> Stop All Promotions
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<span class="badge bg-success bg-opacity-10 text-success border border-success px-3 py-2 rounded-pill d-flex align-items-center">
|
|
<i class="bi bi-circle-fill small me-1"></i> Live
|
|
</span>
|
|
</div>
|
|
</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; ?>
|
|
|
|
<?php if (isset($_GET['success']) && $_GET['success'] === 'promotions_stopped'): ?>
|
|
<div class="alert alert-success border-0 shadow-sm rounded-3">
|
|
<i class="bi bi-check-circle-fill me-2"></i> All running promotions have been stopped successfully.
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Summary Stats -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0 bg-primary bg-opacity-10 text-primary p-3 rounded">
|
|
<i class="bi bi-currency-dollar fs-4"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Total Revenue</h6>
|
|
<div class="fs-4 fw-bold text-primary"><?= format_currency($total_sum) ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0 bg-success bg-opacity-10 text-success p-3 rounded">
|
|
<i class="bi bi-receipt fs-4"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Total Orders</h6>
|
|
<div class="fs-4 fw-bold text-success"><?= $orders_pagination['total_rows'] ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0 bg-info bg-opacity-10 text-info p-3 rounded">
|
|
<i class="bi bi-calendar-event fs-4"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Date Range</h6>
|
|
<div class="small fw-bold">
|
|
<?= !empty($_GET['start_date']) ? date('M d, Y', strtotime($_GET['start_date'])) : 'Start' ?>
|
|
-
|
|
<?= !empty($_GET['end_date']) ? date('M d, Y', strtotime($_GET['end_date'])) : 'Today' ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body bg-light">
|
|
<form method="GET" class="row g-3 align-items-end">
|
|
<div class="col-md-3">
|
|
<label class="form-label small fw-bold text-muted">Outlet</label>
|
|
<select name="outlet_id" class="form-select">
|
|
<option value="">All Outlets</option>
|
|
<?php foreach ($outlets as $outlet): ?>
|
|
<option value="<?= $outlet['id'] ?>" <?= (isset($_GET['outlet_id']) && $_GET['outlet_id'] == $outlet['id']) ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($outlet['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small fw-bold text-muted">Date Range</label>
|
|
<div class="input-group">
|
|
<input type="date" name="start_date" class="form-control" value="<?= $_GET['start_date'] ?? '' ?>" placeholder="Start">
|
|
<span class="input-group-text bg-white border-start-0 border-end-0">-</span>
|
|
<input type="date" name="end_date" class="form-control" value="<?= $_GET['end_date'] ?? '' ?>" placeholder="End">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small fw-bold text-muted">Search</label>
|
|
<input type="text" name="search" class="form-control" placeholder="Order No (ID)" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
<i class="bi bi-filter"></i> Filter
|
|
</button>
|
|
<a href="orders.php" class="btn btn-outline-secondary">
|
|
<i class="bi bi-x-lg"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body p-0">
|
|
<!-- Pagination Controls -->
|
|
<div class="p-3 border-bottom bg-light">
|
|
<?php render_pagination_controls($orders_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>Outlet</th>
|
|
<th>Customer</th>
|
|
<th>Type</th>
|
|
<th>Source</th>
|
|
<th>Items</th>
|
|
<th>Total</th>
|
|
<th>Payment</th>
|
|
<th>Status</th>
|
|
<th>Time</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($orders as $order): ?>
|
|
<tr>
|
|
<td class="ps-4">#<?= $order['id'] ?></td>
|
|
<td>
|
|
<span class="badge bg-white text-dark border">
|
|
<i class="bi bi-shop me-1"></i>
|
|
<?= htmlspecialchars($order['outlet_name'] ?? 'Unknown') ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<?php if (!empty($order['customer_name'])): ?>
|
|
<div><?= htmlspecialchars((string)($order['customer_name'] ?? '')) ?></div>
|
|
<?php if (!empty($order['customer_phone'])): ?>
|
|
<small class="text-muted"><i class="bi bi-telephone me-1"></i><?= htmlspecialchars((string)($order['customer_phone'] ?? '')) ?></small>
|
|
<?php endif; ?>
|
|
<?php else: ?>
|
|
<span class="text-muted small">Guest</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<?php
|
|
$badge = match($order['order_type']) {
|
|
'dine-in' => 'bg-info',
|
|
'takeaway' => 'bg-success',
|
|
'delivery' => 'bg-warning',
|
|
'drive-thru' => 'bg-primary',
|
|
default => 'bg-secondary'
|
|
};
|
|
?>
|
|
<span class="badge <?= $badge ?> text-dark bg-opacity-25 border border-<?= str_replace('bg-', '', $badge) ?>"><?= ucfirst($order['order_type']) ?></span>
|
|
</td>
|
|
<td>
|
|
<?php if ($order['order_type'] === 'dine-in' && $order['table_number']): ?>
|
|
<span class="badge bg-secondary">Table <?= htmlspecialchars((string)($order['table_number'] ?? '')) ?></span>
|
|
<?php else: ?>
|
|
<span class="badge bg-light text-dark border"><?= ucfirst($order['order_type']) ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><small class="text-muted"><?= htmlspecialchars((string)($order['items_summary'] ?? '')) ?></small></td>
|
|
<td><?= format_currency($order['total_amount']) ?></td>
|
|
<td>
|
|
<?php
|
|
$payment_name = $order['payment_type_name'] ?? 'Unpaid';
|
|
$payment_badge = match(strtolower($payment_name)) {
|
|
'cash' => 'bg-success',
|
|
'credit card' => 'bg-primary',
|
|
'loyalty redeem' => 'bg-warning',
|
|
'unpaid' => 'bg-secondary',
|
|
default => 'bg-secondary'
|
|
};
|
|
?>
|
|
<span class="badge <?= $payment_badge ?> text-dark bg-opacity-25 border border-<?= str_replace('bg-', '', $payment_badge) ?>">
|
|
<?= htmlspecialchars((string)($payment_name ?? '')) ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge rounded-pill status-<?= $order['status'] ?>">
|
|
<?= ucfirst($order['status']) ?>
|
|
</span>
|
|
</td>
|
|
<td class="text-muted small">
|
|
<div><?= date('M d', strtotime($order['created_at'])) ?></div>
|
|
<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">
|
|
|
|
<?php if ($order['status'] === 'pending'): ?>
|
|
<button type="submit" name="status" value="preparing" class="btn btn-sm btn-primary">
|
|
<i class="bi bi-play-fill"></i> Start
|
|
</button>
|
|
<button type="submit" name="status" value="cancelled" class="btn btn-sm btn-outline-danger">
|
|
<i class="bi bi-x"></i>
|
|
</button>
|
|
<?php elseif ($order['status'] === 'preparing'): ?>
|
|
<button type="submit" name="status" value="ready" class="btn btn-sm btn-warning text-dark">
|
|
<i class="bi bi-check-circle"></i> Ready
|
|
</button>
|
|
<?php elseif ($order['status'] === 'ready'): ?>
|
|
<button type="submit" name="status" value="completed" class="btn btn-sm btn-success">
|
|
<i class="bi bi-check-all"></i> Complete
|
|
</button>
|
|
<?php else: ?>
|
|
<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; ?>
|
|
<?php if (empty($orders)): ?>
|
|
<tr>
|
|
<td colspan="11" class="text-center py-5 text-muted">
|
|
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
|
No active orders found matching your criteria.
|
|
</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<!-- Bottom Pagination -->
|
|
<div class="p-3 border-top bg-light">
|
|
<?php render_pagination_controls($orders_pagination); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include 'includes/footer.php'; ?>
|