adding cashiers sales report

This commit is contained in:
Flatlogic Bot 2026-03-02 05:44:45 +00:00
parent 1aec2c17d6
commit d229f476df
3 changed files with 354 additions and 5 deletions

View File

@ -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
View 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'; ?>

View File

@ -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);