Baza danych i zapis akcji przywoływania kelnera. Czekam na endpointy lub coś z KDS
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/get_table_name.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Method not allowed'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$data = json_decode($rawBody, true);
|
||||
if (!is_array($data)) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid JSON payload'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$eventName = isset($data['eventName']) ? trim((string)$data['eventName']) : '';
|
||||
$sessionId = isset($data['sessionId']) ? trim((string)$data['sessionId']) : '';
|
||||
$tableId = isset($data['tableId']) ? trim((string)$data['tableId']) : null;
|
||||
$zone = isset($data['zone']) ? trim((string)$data['zone']) : null;
|
||||
$qrHash = isset($data['qrHash']) ? trim((string)$data['qrHash']) : null;
|
||||
$deviceType = isset($data['deviceType']) ? trim((string)$data['deviceType']) : null;
|
||||
$browser = isset($data['browser']) ? trim((string)$data['browser']) : null;
|
||||
$payload = isset($data['payload']) && is_array($data['payload']) ? $data['payload'] : null;
|
||||
|
||||
$allowedEvents = [
|
||||
'qr_scan',
|
||||
'session_start',
|
||||
'geo_check_started',
|
||||
'geo_check_passed',
|
||||
'geo_check_failed',
|
||||
'geo_bypass_host',
|
||||
'waiter_call_requested',
|
||||
'view_status',
|
||||
'view_menu',
|
||||
'menu_search',
|
||||
'bill_dialog_opened',
|
||||
'bill_request_sent',
|
||||
];
|
||||
|
||||
if ($eventName === '' || !in_array($eventName, $allowedEvents, true)) {
|
||||
http_response_code(422);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid eventName'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($sessionId === '' || strlen($sessionId) > 64) {
|
||||
http_response_code(422);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid sessionId'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($qrHash && !$tableId && isset($conn)) {
|
||||
$resolved = getTableNameByHash($conn, $qrHash);
|
||||
if ($resolved !== '') {
|
||||
$tableId = $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tableId !== null && strlen($tableId) > 32) {
|
||||
$tableId = substr($tableId, 0, 32);
|
||||
}
|
||||
if ($zone !== null && strlen($zone) > 16) {
|
||||
$zone = substr($zone, 0, 16);
|
||||
}
|
||||
if ($qrHash !== null && strlen($qrHash) > 128) {
|
||||
$qrHash = substr($qrHash, 0, 128);
|
||||
}
|
||||
if ($deviceType !== null && strlen($deviceType) > 16) {
|
||||
$deviceType = substr($deviceType, 0, 16);
|
||||
}
|
||||
if ($browser !== null && strlen($browser) > 32) {
|
||||
$browser = substr($browser, 0, 32);
|
||||
}
|
||||
|
||||
$ip = '';
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$parts = explode(',', (string)$_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
$ip = trim($parts[0]);
|
||||
} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
|
||||
$ip = trim((string)$_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
$ipHash = $ip !== '' ? hash('sha256', $ip . '|karczma_analytics_v1') : null;
|
||||
$payload = is_array($payload) ? $payload : [];
|
||||
if ($ip !== '' && !isset($payload['ipAddress'])) {
|
||||
$payload['ipAddress'] = $ip;
|
||||
}
|
||||
$payloadJson = $payload ? json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
|
||||
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO analytics_events (
|
||||
created_at,
|
||||
event_name,
|
||||
session_id,
|
||||
table_id,
|
||||
zone,
|
||||
qr_hash,
|
||||
device_type,
|
||||
browser,
|
||||
ip_hash,
|
||||
payload_json
|
||||
) VALUES (
|
||||
NOW(3),
|
||||
:event_name,
|
||||
:session_id,
|
||||
:table_id,
|
||||
:zone,
|
||||
:qr_hash,
|
||||
:device_type,
|
||||
:browser,
|
||||
:ip_hash,
|
||||
:payload_json
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':event_name' => $eventName,
|
||||
':session_id' => $sessionId,
|
||||
':table_id' => $tableId,
|
||||
':zone' => $zone,
|
||||
':qr_hash' => $qrHash,
|
||||
':device_type' => $deviceType,
|
||||
':browser' => $browser,
|
||||
':ip_hash' => $ipHash,
|
||||
':payload_json' => $payloadJson,
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
// Best-effort analytics: return success=false, but do not crash frontend flow.
|
||||
http_response_code(200);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Analytics insert failed'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../public/staff/auth.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
http_response_code(405);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Method not allowed'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isAdminLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Unauthorized'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$range = isset($_GET['days']) ? trim((string)$_GET['days']) : '7';
|
||||
$allowedRanges = ['7', '30', '90', 'all', 'this_year', 'last_year'];
|
||||
if (!in_array($range, $allowedRanges, true)) {
|
||||
$range = '7';
|
||||
}
|
||||
|
||||
$whereWindow = '';
|
||||
$baseParams = [];
|
||||
$now = new DateTimeImmutable('now');
|
||||
|
||||
switch ($range) {
|
||||
case 'all':
|
||||
break;
|
||||
case 'this_year':
|
||||
$start = new DateTimeImmutable(date('Y-01-01 00:00:00'));
|
||||
$end = $start->modify('+1 year');
|
||||
$whereWindow = ' AND created_at >= :start_at AND created_at < :end_at';
|
||||
$baseParams = [
|
||||
':start_at' => $start->format('Y-m-d H:i:s'),
|
||||
':end_at' => $end->format('Y-m-d H:i:s'),
|
||||
];
|
||||
break;
|
||||
case 'last_year':
|
||||
$start = new DateTimeImmutable((date('Y') - 1) . '-01-01 00:00:00');
|
||||
$end = $start->modify('+1 year');
|
||||
$whereWindow = ' AND created_at >= :start_at AND created_at < :end_at';
|
||||
$baseParams = [
|
||||
':start_at' => $start->format('Y-m-d H:i:s'),
|
||||
':end_at' => $end->format('Y-m-d H:i:s'),
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$daysInt = (int)$range;
|
||||
if ($daysInt < 1) $daysInt = 1;
|
||||
if ($daysInt > 3650) $daysInt = 3650;
|
||||
$start = $now->modify("-{$daysInt} days");
|
||||
$whereWindow = ' AND created_at >= :start_at';
|
||||
$baseParams = [
|
||||
':start_at' => $start->format('Y-m-d H:i:s'),
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
|
||||
$sqlTopTables = "
|
||||
SELECT
|
||||
COALESCE(NULLIF(table_id, ''), 'unknown') AS table_id,
|
||||
SUM(CASE WHEN event_name = 'qr_scan' THEN 1 ELSE 0 END) AS qr_scans,
|
||||
SUM(CASE WHEN event_name = 'session_start' THEN 1 ELSE 0 END) AS sessions,
|
||||
SUM(CASE WHEN event_name = 'geo_check_passed' THEN 1 ELSE 0 END) AS geo_pass,
|
||||
SUM(CASE WHEN event_name = 'geo_check_failed' THEN 1 ELSE 0 END) AS geo_fail
|
||||
FROM analytics_events
|
||||
WHERE 1=1 {$whereWindow}
|
||||
GROUP BY COALESCE(NULLIF(table_id, ''), 'unknown')
|
||||
ORDER BY qr_scans DESC, sessions DESC
|
||||
LIMIT 20
|
||||
";
|
||||
$stmtTopTables = $pdo->prepare($sqlTopTables);
|
||||
$stmtTopTables->execute($baseParams);
|
||||
$topTables = $stmtTopTables->fetchAll();
|
||||
|
||||
$sqlByZone = "
|
||||
SELECT
|
||||
COALESCE(NULLIF(zone, ''), 'unknown') AS zone,
|
||||
SUM(CASE WHEN event_name = 'qr_scan' THEN 1 ELSE 0 END) AS qr_scans,
|
||||
SUM(CASE WHEN event_name = 'session_start' THEN 1 ELSE 0 END) AS sessions,
|
||||
SUM(CASE WHEN event_name = 'geo_check_passed' THEN 1 ELSE 0 END) AS geo_pass,
|
||||
SUM(CASE WHEN event_name = 'geo_check_failed' THEN 1 ELSE 0 END) AS geo_fail
|
||||
FROM analytics_events
|
||||
WHERE 1=1 {$whereWindow}
|
||||
GROUP BY COALESCE(NULLIF(zone, ''), 'unknown')
|
||||
ORDER BY sessions DESC, qr_scans DESC
|
||||
";
|
||||
$stmtByZone = $pdo->prepare($sqlByZone);
|
||||
$stmtByZone->execute($baseParams);
|
||||
$zoneStats = $stmtByZone->fetchAll();
|
||||
|
||||
$sqlFunnel = "
|
||||
SELECT
|
||||
event_name,
|
||||
COUNT(*) AS total
|
||||
FROM analytics_events
|
||||
WHERE 1=1 {$whereWindow}
|
||||
AND event_name IN ('qr_scan','session_start','view_menu','bill_dialog_opened','bill_request_sent','waiter_call_requested')
|
||||
GROUP BY event_name
|
||||
";
|
||||
$stmtFunnel = $pdo->prepare($sqlFunnel);
|
||||
$stmtFunnel->execute($baseParams);
|
||||
$funnelRows = $stmtFunnel->fetchAll();
|
||||
$funnelMap = [];
|
||||
foreach ($funnelRows as $row) {
|
||||
$funnelMap[$row['event_name']] = (int)$row['total'];
|
||||
}
|
||||
|
||||
$sqlGeo = "
|
||||
SELECT
|
||||
SUM(CASE WHEN event_name = 'geo_check_passed' THEN 1 ELSE 0 END) AS geo_passed,
|
||||
SUM(CASE WHEN event_name = 'geo_check_failed' THEN 1 ELSE 0 END) AS geo_failed,
|
||||
SUM(CASE WHEN event_name = 'geo_bypass_host' THEN 1 ELSE 0 END) AS geo_bypass
|
||||
FROM analytics_events
|
||||
WHERE 1=1 {$whereWindow}
|
||||
";
|
||||
$stmtGeo = $pdo->prepare($sqlGeo);
|
||||
$stmtGeo->execute($baseParams);
|
||||
$geo = $stmtGeo->fetch() ?: ['geo_passed' => 0, 'geo_failed' => 0, 'geo_bypass' => 0];
|
||||
|
||||
$sqlRecentOpens = "
|
||||
SELECT
|
||||
created_at,
|
||||
table_id,
|
||||
zone,
|
||||
device_type,
|
||||
browser,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$.ipAddress')) AS ip_address
|
||||
FROM analytics_events
|
||||
WHERE event_name = 'qr_scan' {$whereWindow}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 50
|
||||
";
|
||||
$stmtRecentOpens = $pdo->prepare($sqlRecentOpens);
|
||||
$stmtRecentOpens->execute($baseParams);
|
||||
$recentOpens = $stmtRecentOpens ? $stmtRecentOpens->fetchAll() : [];
|
||||
|
||||
$sqlQueueSummary = "
|
||||
SELECT
|
||||
COUNT(*) AS total_actions,
|
||||
SUM(CASE WHEN api_sent = 0 THEN 1 ELSE 0 END) AS pending_api,
|
||||
SUM(CASE WHEN status_kds = 0 THEN 1 ELSE 0 END) AS pending_kds,
|
||||
SUM(CASE WHEN status_kds = 1 THEN 1 ELSE 0 END) AS done_kds
|
||||
FROM guest_action_queue
|
||||
WHERE 1=1 {$whereWindow}
|
||||
";
|
||||
$stmtQueueSummary = $pdo->prepare($sqlQueueSummary);
|
||||
$stmtQueueSummary->execute($baseParams);
|
||||
$queueSummary = $stmtQueueSummary->fetch() ?: [
|
||||
'total_actions' => 0,
|
||||
'pending_api' => 0,
|
||||
'pending_kds' => 0,
|
||||
'done_kds' => 0,
|
||||
];
|
||||
|
||||
$sqlQueueItems = "
|
||||
SELECT
|
||||
id,
|
||||
table_id,
|
||||
message_type,
|
||||
message_text,
|
||||
api_sent,
|
||||
status_kds,
|
||||
created_at
|
||||
FROM guest_action_queue
|
||||
WHERE 1=1 {$whereWindow}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 100
|
||||
";
|
||||
$stmtQueueItems = $pdo->prepare($sqlQueueItems);
|
||||
$stmtQueueItems->execute($baseParams);
|
||||
$queueItems = $stmtQueueItems->fetchAll();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'days' => $range,
|
||||
'topTables' => $topTables,
|
||||
'zoneStats' => $zoneStats,
|
||||
'funnel' => [
|
||||
'qr_scan' => (int)($funnelMap['qr_scan'] ?? 0),
|
||||
'session_start' => (int)($funnelMap['session_start'] ?? 0),
|
||||
'view_menu' => (int)($funnelMap['view_menu'] ?? 0),
|
||||
'bill_dialog_opened' => (int)($funnelMap['bill_dialog_opened'] ?? 0),
|
||||
'bill_request_sent' => (int)($funnelMap['bill_request_sent'] ?? 0),
|
||||
'waiter_call_requested' => (int)($funnelMap['waiter_call_requested'] ?? 0),
|
||||
],
|
||||
'geolocation' => [
|
||||
'passed' => (int)$geo['geo_passed'],
|
||||
'failed' => (int)$geo['geo_failed'],
|
||||
'bypass' => (int)$geo['geo_bypass'],
|
||||
],
|
||||
'recentOpens' => $recentOpens,
|
||||
'guestQueueSummary' => [
|
||||
'total' => (int)$queueSummary['total_actions'],
|
||||
'pendingApi' => (int)$queueSummary['pending_api'],
|
||||
'pendingKds' => (int)$queueSummary['pending_kds'],
|
||||
'doneKds' => (int)$queueSummary['done_kds'],
|
||||
],
|
||||
'guestQueue' => $queueItems,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Nie udało się pobrać raportów analitycznych.'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/get_table_name.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Method not allowed'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$data = json_decode($rawBody, true);
|
||||
if (!is_array($data)) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid JSON payload'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$tableId = isset($data['tableId']) ? trim((string)$data['tableId']) : '';
|
||||
$messageType = isset($data['messageType']) ? trim((string)$data['messageType']) : '';
|
||||
$messageText = isset($data['messageText']) ? trim((string)$data['messageText']) : '';
|
||||
$qrHash = isset($data['qrHash']) ? trim((string)$data['qrHash']) : '';
|
||||
|
||||
$allowedTypes = ['waiter_call', 'bill_request'];
|
||||
if ($messageType === '' || !in_array($messageType, $allowedTypes, true)) {
|
||||
http_response_code(422);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid messageType'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($tableId === '' && $qrHash !== '' && isset($conn)) {
|
||||
$resolved = getTableNameByHash($conn, $qrHash);
|
||||
if ($resolved !== '') {
|
||||
$tableId = $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tableId === '') {
|
||||
http_response_code(422);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'tableId is required'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($messageText === '') {
|
||||
http_response_code(422);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'messageText is required'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (strlen($tableId) > 32) {
|
||||
$tableId = substr($tableId, 0, 32);
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO guest_action_queue (
|
||||
table_id,
|
||||
message_type,
|
||||
message_text,
|
||||
api_sent,
|
||||
status_kds,
|
||||
created_at
|
||||
) VALUES (
|
||||
:table_id,
|
||||
:message_type,
|
||||
:message_text,
|
||||
0,
|
||||
0,
|
||||
NOW(3)
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':table_id' => $tableId,
|
||||
':message_type' => $messageType,
|
||||
':message_text' => $messageText,
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'id' => (int)$pdo->lastInsertId()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Queue insert failed'
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user