38682-vm/admin/orders.php
2026-04-03 17:21:33 +00:00

446 lines
22 KiB
PHP

<?php
declare(strict_types=1);
error_reporting(E_ALL);
ini_set("display_errors", "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;
}
// Handle Delete Order
if (isset($_GET['delete'])) {
if (!has_permission('manage_orders')) {
header("Location: orders.php?error=permission_denied");
exit;
}
$id = (int)$_GET['delete'];
$pdo->beginTransaction();
try {
$pdo->prepare("DELETE FROM order_items WHERE order_id = ?")->execute([$id]);
$pdo->prepare("DELETE FROM orders WHERE id = ?")->execute([$id]);
$pdo->commit();
header("Location: orders.php?success=order_deleted");
} catch (Exception $e) {
$pdo->rollBack();
header("Location: orders.php?error=delete_failed");
}
exit;
}
// Fetch Outlets for Filter
$outlets = $pdo->query("SELECT id, name FROM outlets WHERE is_deleted = 0 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 / Customer Name)
if (!empty($_GET['search'])) {
$searchTerm = $_GET['search'];
if (is_numeric($searchTerm)) {
$where[] = "(o.id = :search_exact OR o.customer_name LIKE :search_like)";
$params[':search_exact'] = $searchTerm;
$params[':search_like'] = "%$searchTerm%";
} else {
$where[] = "o.customer_name LIKE :search";
$params[':search'] = "%$searchTerm%";
}
}
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
// Calculate Total Sum and Total Commission and Total VAT for filtered orders
$sum_query = "SELECT SUM(total_amount) as total_sum, SUM(commission_amount) as total_commission, SUM(vat) as total_vat FROM orders o $where_clause";
$stmt_sum = $pdo->prepare($sum_query);
$stmt_sum->execute($params);
$sum_data = $stmt_sum->fetch(PDO::FETCH_ASSOC);
$total_sum = (float)($sum_data['total_sum'] ?? 0);
$total_commission = (float)($sum_data['total_commission'] ?? 0);
$total_vat_sum = (float)($sum_data['total_vat'] ?? 0);
// Main Query
$query = "SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name, u.username as cashier_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
LEFT JOIN users u ON o.user_id = u.id
$where_clause
ORDER BY o.created_at DESC";
$orders_pagination = paginate_query($pdo, $query, $params);
$orders = $orders_pagination['data'];
$settings = get_company_settings();
$commission_enabled = !empty($settings['commission_enabled']);
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'])): ?>
<?php if ($_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 elseif ($_GET['error'] === 'delete_failed'): ?>
<div class="alert alert-danger border-0 shadow-sm rounded-3">Error: Failed to delete order.</div>
<?php endif; ?>
<?php endif; ?>
<?php if (isset($_GET['success'])): ?>
<?php if ($_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 elseif ($_GET['success'] === 'order_deleted'): ?>
<div class="alert alert-success border-0 shadow-sm rounded-3">
<i class="bi bi-check-circle-fill me-2"></i> Order has been deleted successfully.
</div>
<?php endif; ?>
<?php endif; ?>
<!-- Summary Stats -->
<div class="row mb-4">
<div class="col-md-3">
<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-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-shrink-0 bg-warning bg-opacity-10 text-warning p-3 rounded">
<i class="bi bi-percent fs-4"></i>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Total VAT</h6>
<div class="fs-4 fw-bold text-warning"><?= format_currency($total_vat_sum) ?></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<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-3">
<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 / Customer" 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>Cashier</th>
<th>Customer</th>
<th>Type</th>
<th>Car Plate</th>
<th>VAT</th>
<th>Total</th>
<?php if ($commission_enabled): ?>
<th>Commission</th>
<?php endif; ?>
<th>Payment</th>
<th>Status</th>
<th>Time</th>
<th class="text-end pe-4">Actions</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>
<small class="fw-bold">@<?= htmlspecialchars((string)($order['cashier_name'] ?? 'Guest')) ?></small>
</td>
<td>
<?php if (!empty($order['customer_name'])): ?>
<div><?= htmlspecialchars((string)($order['customer_name'] ?? '')) ?></div>
<?php if (!empty($order['car_plate'])): ?>
<div class="small text-muted"><i class="bi bi-car-front me-1"></i><?= htmlspecialchars((string)($order['car_plate'] ?? '')) ?></div>
<?php endif; ?>
<?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"
};
$source_name = match($order["source"] ?? "pos") {
"online" => "Online",
"qr" => "QR Order",
default => "POS"
};
$source_badge = match($order["source"] ?? "pos") {
"online" => "bg-success",
"qr" => "bg-info",
default => "bg-secondary"
};
?>
<span class="badge <?= $badge ?> text-dark bg-opacity-25 border border-<?= str_replace("bg-", "", $badge) ?> mb-1"><?= match($order["order_type"] ?? "") { "dine-in" => "Dine-In", "takeaway" => "Take Away", "delivery" => "Delivery", "drive-thru" => "Drive-Thru", default => "Take Away" } ?></span><br>
<span class="badge <?= $source_badge ?> text-white"><?= $source_name ?></span>
</td>
<td>
<span class="text-muted small"><?= format_currency($order['vat']) ?></span>
</td>
<td class="fw-bold"><?= format_currency($order['total_amount']) ?></td>
<?php if ($commission_enabled): ?>
<td>
<span class="text-warning fw-bold"><?= format_currency($order['commission_amount']) ?></span>
</td>
<?php endif; ?>
<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',
'bank transfer' => 'bg-info',
'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 class="text-end pe-4">
<div class="d-flex gap-2 justify-content-end align-items-center">
<!-- Status Workflow Buttons -->
<?php if (has_permission('orders_add')): ?>
<form method="POST" class="d-flex gap-1 me-2 border-end pe-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 py-0 px-1" title="Start Preparing">
<i class="bi bi-play-fill"></i>
</button>
<button type="submit" name="status" value="cancelled" class="btn btn-sm btn-outline-danger py-0 px-1" title="Cancel Order">
<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 py-0 px-1" title="Mark Ready">
<i class="bi bi-check-circle"></i>
</button>
<?php elseif ($order['status'] === 'ready'): ?>
<button type="submit" name="status" value="completed" class="btn btn-sm btn-success py-0 px-1" title="Complete Order">
<i class="bi bi-check-all"></i>
</button>
<?php endif; ?>
</form>
<?php endif; ?>
<!-- Standard Actions -->
<a href="order_view.php?id=<?= $order['id'] ?>" class="btn-icon-soft" title="View Order">
<i class="bi bi-eye-fill"></i>
</a>
<?php if (has_permission('orders_add')): ?>
<a href="order_edit.php?id=<?= $order['id'] ?>" class="btn-icon-soft edit" title="Edit Order">
<i class="bi bi-pencil-fill"></i>
</a>
<?php endif; ?>
<?php if (has_permission('manage_orders')): ?>
<a href="?delete=<?= $order['id'] ?>" class="btn-icon-soft delete" onclick="return confirm('Are you sure you want to delete this order? This action cannot be undone.')" title="Delete Order">
<i class="bi bi-trash-fill"></i>
</a>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($orders)): ?>
<tr>
<td colspan="12" 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'; ?>