adding cashiers sales report
This commit is contained in:
parent
1aec2c17d6
commit
d229f476df
@ -475,7 +475,7 @@ function can_view($module) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$reportsGroup = ['reports.php', 'report_products.php', 'report_staff.php'];
|
$reportsGroup = ['reports.php', 'report_products.php', 'report_staff.php', 'report_cashiers.php'];
|
||||||
if (can_view('reports')):
|
if (can_view('reports')):
|
||||||
?>
|
?>
|
||||||
<div class="nav-group">
|
<div class="nav-group">
|
||||||
@ -501,6 +501,11 @@ function can_view($module) {
|
|||||||
<i class="bi bi-person-badge me-2"></i> Staff Sales
|
<i class="bi bi-person-badge me-2"></i> Staff Sales
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('report_cashiers.php') ?>" href="report_cashiers.php">
|
||||||
|
<i class="bi bi-wallet2 me-2"></i> Cashier Sales
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
344
admin/report_cashiers.php
Normal file
344
admin/report_cashiers.php
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/includes/header.php';
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
if (function_exists('require_permission')) {
|
||||||
|
require_permission('reports_view');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
$startDate = $_GET['start_date'] ?? date('Y-m-d');
|
||||||
|
$endDate = $_GET['end_date'] ?? date('Y-m-d');
|
||||||
|
$outletId = $_GET['outlet_id'] ?? '';
|
||||||
|
$cashierId = $_GET['cashier_id'] ?? '';
|
||||||
|
|
||||||
|
// Fetch Outlets
|
||||||
|
$outletsStmt = $pdo->query("SELECT id, name, name_ar FROM outlets WHERE is_deleted = 0 ORDER BY name ASC");
|
||||||
|
$allOutlets = $outletsStmt->fetchAll();
|
||||||
|
|
||||||
|
// Fetch Cashiers (Users)
|
||||||
|
$cashiersStmt = $pdo->query("SELECT id, full_name, full_name_ar FROM users WHERE is_deleted = 0 AND is_active = 1 ORDER BY full_name ASC");
|
||||||
|
$allCashiers = $cashiersStmt->fetchAll();
|
||||||
|
|
||||||
|
// Fetch all payment types
|
||||||
|
$allPaymentTypesStmt = $pdo->query("SELECT name FROM payment_types WHERE is_deleted = 0 ORDER BY id ASC");
|
||||||
|
$allPaymentTypes = $allPaymentTypesStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Define a set of Bootstrap colors to cycle through for payment types
|
||||||
|
$paymentColors = [
|
||||||
|
'text-success',
|
||||||
|
'text-info',
|
||||||
|
'text-warning',
|
||||||
|
'text-danger',
|
||||||
|
'text-secondary',
|
||||||
|
'text-dark',
|
||||||
|
'text-primary'
|
||||||
|
];
|
||||||
|
|
||||||
|
$paymentBadgeColors = [
|
||||||
|
'bg-success',
|
||||||
|
'bg-info text-dark',
|
||||||
|
'bg-warning text-dark',
|
||||||
|
'bg-danger',
|
||||||
|
'bg-secondary',
|
||||||
|
'bg-dark',
|
||||||
|
'bg-primary'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Base query additions
|
||||||
|
$conditions = ["DATE(o.created_at) BETWEEN ? AND ? AND o.status != 'cancelled'"];
|
||||||
|
$queryParams = [$startDate, $endDate];
|
||||||
|
|
||||||
|
if (!empty($outletId)) {
|
||||||
|
$conditions[] = "o.outlet_id = ?";
|
||||||
|
$queryParams[] = $outletId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($cashierId)) {
|
||||||
|
$conditions[] = "o.user_id = ?";
|
||||||
|
$queryParams[] = $cashierId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$whereClause = implode(' AND ', $conditions);
|
||||||
|
|
||||||
|
// Fetch total amounts grouped by cashier, outlet and payment type
|
||||||
|
$salesStmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
u.id as cashier_id,
|
||||||
|
u.full_name as cashier_name,
|
||||||
|
u.full_name_ar as cashier_name_ar,
|
||||||
|
outl.id as outlet_id,
|
||||||
|
outl.name as outlet_name,
|
||||||
|
outl.name_ar as outlet_name_ar,
|
||||||
|
pt.name as payment_name,
|
||||||
|
SUM(o.total_amount) as total_amount,
|
||||||
|
COUNT(o.id) as orders_count
|
||||||
|
FROM orders o
|
||||||
|
JOIN users u ON o.user_id = u.id
|
||||||
|
LEFT JOIN outlets outl ON o.outlet_id = outl.id
|
||||||
|
LEFT JOIN payment_types pt ON o.payment_type_id = pt.id
|
||||||
|
WHERE $whereClause
|
||||||
|
GROUP BY o.user_id, o.outlet_id, o.payment_type_id
|
||||||
|
ORDER BY u.full_name ASC, outl.name ASC
|
||||||
|
");
|
||||||
|
$salesStmt->execute($queryParams);
|
||||||
|
$salesData = $salesStmt->fetchAll();
|
||||||
|
|
||||||
|
// Group by Cashier + Outlet
|
||||||
|
$cashierSales = [];
|
||||||
|
foreach ($salesData as $row) {
|
||||||
|
$key = $row['cashier_id'] . '_' . $row['outlet_id'];
|
||||||
|
if (!isset($cashierSales[$key])) {
|
||||||
|
$cashierSales[$key] = [
|
||||||
|
'name' => $row['cashier_name'],
|
||||||
|
'name_ar' => $row['cashier_name_ar'],
|
||||||
|
'outlet_name' => $row['outlet_name'] ?? 'Unknown Outlet',
|
||||||
|
'outlet_name_ar' => $row['outlet_name_ar'] ?? '',
|
||||||
|
'payments' => [],
|
||||||
|
'total' => 0,
|
||||||
|
'orders_count' => 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$pName = $row['payment_name'] ?? 'Unknown';
|
||||||
|
$amount = (float)$row['total_amount'];
|
||||||
|
$count = (int)$row['orders_count'];
|
||||||
|
|
||||||
|
$cashierSales[$key]['payments'][$pName] = $amount;
|
||||||
|
$cashierSales[$key]['total'] += $amount;
|
||||||
|
$cashierSales[$key]['orders_count'] += $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<style>
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
background-color: #fff !important;
|
||||||
|
font-family: Arial, sans-serif !important;
|
||||||
|
}
|
||||||
|
.d-print-none,
|
||||||
|
.navbar,
|
||||||
|
header,
|
||||||
|
.main-content > header,
|
||||||
|
.sidebar,
|
||||||
|
aside,
|
||||||
|
nav {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.main-content {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.container-fluid {
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
background: transparent !important;
|
||||||
|
border-bottom: 2px solid #000 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-responsive {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
width: 100% !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
}
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
border: 1px solid #ddd !important;
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
.table thead th {
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
color: #000 !important;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
border: 1px solid #ccc !important;
|
||||||
|
color: #000 !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
h2, h5 {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.print-header {
|
||||||
|
display: block !important;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.print-header h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.print-header p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
@page {
|
||||||
|
margin: 1cm;
|
||||||
|
size: landscape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.print-header { display: none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container-fluid p-0">
|
||||||
|
|
||||||
|
<div class="print-header">
|
||||||
|
<h2>Cashier Sales Report</h2>
|
||||||
|
<p>Date Range: <?= htmlspecialchars($startDate) ?> to <?= htmlspecialchars($endDate) ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3 d-print-none">
|
||||||
|
<div>
|
||||||
|
<h2 class="fw-bold mb-1">Cashier Sales Report</h2>
|
||||||
|
<p class="text-muted mb-0">Sales grouped by cashier and payment methods</p>
|
||||||
|
</div>
|
||||||
|
<form class="row g-2 align-items-center" method="GET">
|
||||||
|
<div class="col-auto">
|
||||||
|
<select name="cashier_id" class="form-select" style="min-width: 150px;">
|
||||||
|
<option value="">All Cashiers</option>
|
||||||
|
<?php foreach ($allCashiers as $c): ?>
|
||||||
|
<option value="<?= $c['id'] ?>" <?= $cashierId == $c['id'] ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($c['full_name']) ?> <?= $c['full_name_ar'] ? '('.$c['full_name_ar'].')' : '' ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<select name="outlet_id" class="form-select" style="min-width: 150px;">
|
||||||
|
<option value="">All Outlets</option>
|
||||||
|
<?php foreach ($allOutlets as $o): ?>
|
||||||
|
<option value="<?= $o['id'] ?>" <?= $outletId == $o['id'] ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($o['name']) ?> <?= $o['name_ar'] ? '('.$o['name_ar'].')' : '' ?>
|
||||||
|
</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="report_cashiers.php" class="btn btn-light text-nowrap">Reset</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="fw-bold mb-0"><i class="bi bi-wallet2 me-2 text-primary d-print-none"></i> <span class="d-print-none">Cashier Breakdown</span></h5>
|
||||||
|
<button onclick="window.print()" class="btn btn-sm btn-outline-secondary d-print-none"><i class="bi bi-printer me-1"></i> Print</button>
|
||||||
|
</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">Cashier Name</th>
|
||||||
|
<th>Outlet</th>
|
||||||
|
<?php foreach ($allPaymentTypes as $index => $ptName): ?>
|
||||||
|
<th class="text-end">
|
||||||
|
<span class="badge <?= $paymentBadgeColors[$index % count($paymentBadgeColors)] ?> rounded-pill px-3 py-2">
|
||||||
|
<?= htmlspecialchars($ptName) ?>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<th class="text-end">Orders</th>
|
||||||
|
<th class="text-end pe-3">Total Sales</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($cashierSales)): ?>
|
||||||
|
<tr><td colspan="<?= count($allPaymentTypes) + 4 ?>" class="text-center py-5 text-muted">No sales found for the selected criteria</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($cashierSales as $key => $data): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="ps-3 fw-bold">
|
||||||
|
<?= htmlspecialchars($data['name']) ?>
|
||||||
|
<?php if ($data['name_ar']): ?>
|
||||||
|
<small class="text-muted d-block fw-normal"><?= htmlspecialchars($data['name_ar']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?= htmlspecialchars($data['outlet_name']) ?>
|
||||||
|
<?php if ($data['outlet_name_ar']): ?>
|
||||||
|
<small class="text-muted d-block fw-normal"><?= htmlspecialchars($data['outlet_name_ar']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<?php foreach ($allPaymentTypes as $index => $ptName): ?>
|
||||||
|
<td class="text-end fw-semibold <?= $paymentColors[$index % count($paymentColors)] ?>">
|
||||||
|
<?= format_currency($data['payments'][$ptName] ?? 0) ?>
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<td class="text-end fw-bold text-secondary"><?= $data['orders_count'] ?></td>
|
||||||
|
<td class="text-end pe-3 fw-bold text-primary"><?= format_currency($data['total']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="bg-light fw-bold">
|
||||||
|
<?php if (!empty($cashierSales)):
|
||||||
|
$totals = [];
|
||||||
|
$grandTotal = 0;
|
||||||
|
$grandTotalOrders = 0;
|
||||||
|
foreach ($allPaymentTypes as $pt) {
|
||||||
|
$totals[$pt] = 0;
|
||||||
|
}
|
||||||
|
foreach ($cashierSales as $data) {
|
||||||
|
foreach ($allPaymentTypes as $pt) {
|
||||||
|
$totals[$pt] += $data['payments'][$pt] ?? 0;
|
||||||
|
}
|
||||||
|
$grandTotal += $data['total'];
|
||||||
|
$grandTotalOrders += $data['orders_count'];
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="ps-3">Totals</td>
|
||||||
|
<td></td>
|
||||||
|
<?php foreach ($allPaymentTypes as $index => $pt): ?>
|
||||||
|
<td class="text-end <?= $paymentColors[$index % count($paymentColors)] ?>">
|
||||||
|
<?= format_currency($totals[$pt]) ?>
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<td class="text-end fw-bold text-secondary fs-6"><?= $grandTotalOrders ?></td>
|
||||||
|
<td class="text-end pe-3 text-primary fs-6"><?= format_currency($grandTotal) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||||
@ -29,7 +29,7 @@ try {
|
|||||||
$daily_sales = $pdo->prepare("SELECT DATE(o.created_at) as date, SUM(o.total_amount) as total
|
$daily_sales = $pdo->prepare("SELECT DATE(o.created_at) as date, SUM(o.total_amount) as total
|
||||||
FROM orders o
|
FROM orders o
|
||||||
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
||||||
AND o.status = 'completed'
|
AND o.status != 'cancelled'
|
||||||
GROUP BY DATE(o.created_at)
|
GROUP BY DATE(o.created_at)
|
||||||
ORDER BY date ASC");
|
ORDER BY date ASC");
|
||||||
$daily_sales->execute($params);
|
$daily_sales->execute($params);
|
||||||
@ -40,7 +40,7 @@ try {
|
|||||||
FROM orders o
|
FROM orders o
|
||||||
JOIN users u ON o.user_id = u.id
|
JOIN users u ON o.user_id = u.id
|
||||||
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
||||||
AND o.status = 'completed'
|
AND o.status != 'cancelled'
|
||||||
GROUP BY u.id
|
GROUP BY u.id
|
||||||
ORDER BY total DESC");
|
ORDER BY total DESC");
|
||||||
$staff_sales->execute($params);
|
$staff_sales->execute($params);
|
||||||
@ -51,7 +51,7 @@ try {
|
|||||||
FROM orders o
|
FROM orders o
|
||||||
JOIN outlets ou ON o.outlet_id = ou.id
|
JOIN outlets ou ON o.outlet_id = ou.id
|
||||||
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
||||||
AND o.status = 'completed'
|
AND o.status != 'cancelled'
|
||||||
GROUP BY ou.id
|
GROUP BY ou.id
|
||||||
ORDER BY total DESC");
|
ORDER BY total DESC");
|
||||||
$outlet_sales->execute($params);
|
$outlet_sales->execute($params);
|
||||||
@ -64,7 +64,7 @@ try {
|
|||||||
JOIN products p ON oi.product_id = p.id
|
JOIN products p ON oi.product_id = p.id
|
||||||
JOIN categories c ON p.category_id = c.id
|
JOIN categories c ON p.category_id = c.id
|
||||||
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
WHERE o.created_at BETWEEN :from AND :to $outlet_query
|
||||||
AND o.status = 'completed'
|
AND o.status != 'cancelled'
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY total DESC");
|
ORDER BY total DESC");
|
||||||
$cat_sales->execute($params);
|
$cat_sales->execute($params);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user