Poprawki API i panelu admina
This commit is contained in:
@@ -3,6 +3,7 @@ header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../public/staff/auth.php';
|
||||
require_once __DIR__ . '/message_text_helper.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
http_response_code(405);
|
||||
@@ -224,6 +225,10 @@ try {
|
||||
$stmtQueueItems = $pdo->prepare($sqlQueueItems);
|
||||
$stmtQueueItems->execute($baseParams);
|
||||
$queueItems = $stmtQueueItems->fetchAll();
|
||||
foreach ($queueItems as &$queueRow) {
|
||||
$queueRow['message_text'] = normalizeQueueMessageText((string) ($queueRow['message_text'] ?? ''));
|
||||
}
|
||||
unset($queueRow);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
|
||||
@@ -4,6 +4,7 @@ header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/get_table_name.php';
|
||||
require_once __DIR__ . '/resolve_table_operator.php';
|
||||
require_once __DIR__ . '/message_text_helper.php';
|
||||
|
||||
$kdsSecret = 'karczma_kuchnia';
|
||||
|
||||
@@ -14,19 +15,69 @@ function verifyKdsSecret(): bool
|
||||
return $secret === $kdsSecret;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
if (!verifyKdsSecret()) {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Forbidden',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
function resolveGuestQueueTableId(string $tableId, string $qrHash): string
|
||||
{
|
||||
global $conn;
|
||||
|
||||
if ($tableId === '' && $qrHash !== '' && isset($conn)) {
|
||||
$resolved = getTableNameByHash($conn, $qrHash);
|
||||
if ($resolved !== '') {
|
||||
$tableId = $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
$stmt = $pdo->query("
|
||||
return trim($tableId);
|
||||
}
|
||||
|
||||
function hasPendingGuestAction(PDO $pdo, string $tableId, string $messageType): bool
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT 1
|
||||
FROM guest_action_queue
|
||||
WHERE table_id = :table_id
|
||||
AND message_type = :message_type
|
||||
AND status_kds = 0
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
':table_id' => $tableId,
|
||||
':message_type' => $messageType,
|
||||
]);
|
||||
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function fetchPendingGuestActions(PDO $pdo, string $tableId): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT message_type
|
||||
FROM guest_action_queue
|
||||
WHERE table_id = :table_id
|
||||
AND status_kds = 0
|
||||
AND message_type IN ('waiter_call', 'bill_request')
|
||||
");
|
||||
$stmt->execute([':table_id' => $tableId]);
|
||||
|
||||
$pending = [
|
||||
'waiter_call' => false,
|
||||
'bill_request' => false,
|
||||
];
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
$type = (string) ($row['message_type'] ?? '');
|
||||
if (isset($pending[$type])) {
|
||||
$pending[$type] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $pending;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
if (verifyKdsSecret()) {
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
id,
|
||||
table_id,
|
||||
@@ -45,6 +96,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$row['id'] = (int) $row['id'];
|
||||
$row['message_text'] = normalizeQueueMessageText((string) ($row['message_text'] ?? ''));
|
||||
}
|
||||
unset($row);
|
||||
|
||||
@@ -61,6 +113,44 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
'status' => 'error',
|
||||
'message' => 'Queue fetch failed',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
$qrHash = isset($_GET['h']) ? trim((string) $_GET['h']) : '';
|
||||
$tableId = resolveGuestQueueTableId(
|
||||
isset($_GET['tableId']) ? trim((string) $_GET['tableId']) : '',
|
||||
$qrHash
|
||||
);
|
||||
|
||||
if ($tableId === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'tableId or h is required',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (strlen($tableId) > 32) {
|
||||
$tableId = substr($tableId, 0, 32);
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
$pending = fetchPendingGuestActions($pdo, $tableId);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'table_id' => $tableId,
|
||||
'pending' => $pending,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Pending status fetch failed',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
@@ -151,7 +241,7 @@ if (!is_array($data)) {
|
||||
|
||||
$tableId = isset($data['tableId']) ? trim((string) $data['tableId']) : '';
|
||||
$messageType = isset($data['messageType']) ? trim((string) $data['messageType']) : '';
|
||||
$messageText = isset($data['messageText']) ? trim((string) $data['messageText']) : '';
|
||||
$messageText = isset($data['messageText']) ? (string) $data['messageText'] : '';
|
||||
$qrHash = isset($data['qrHash']) ? trim((string) $data['qrHash']) : '';
|
||||
$otwierajacyImie = isset($data['otwierajacyImie']) ? trim((string) $data['otwierajacyImie']) : '';
|
||||
$otwierajacyNazwisko = isset($data['otwierajacyNazwisko']) ? trim((string) $data['otwierajacyNazwisko']) : '';
|
||||
@@ -166,12 +256,7 @@ if ($messageType === '' || !in_array($messageType, $allowedTypes, true)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($tableId === '' && $qrHash !== '' && isset($conn)) {
|
||||
$resolved = getTableNameByHash($conn, $qrHash);
|
||||
if ($resolved !== '') {
|
||||
$tableId = $resolved;
|
||||
}
|
||||
}
|
||||
$tableId = resolveGuestQueueTableId($tableId, $qrHash);
|
||||
|
||||
if ($tableId === '') {
|
||||
http_response_code(422);
|
||||
@@ -182,6 +267,8 @@ if ($tableId === '') {
|
||||
exit;
|
||||
}
|
||||
|
||||
$messageText = normalizeQueueMessageText($messageText);
|
||||
|
||||
if ($messageText === '') {
|
||||
http_response_code(422);
|
||||
echo json_encode([
|
||||
@@ -216,6 +303,19 @@ if (strlen($otwierajacyNazwisko) > 100) {
|
||||
|
||||
try {
|
||||
$pdo = getAnalyticsPdo();
|
||||
|
||||
if (hasPendingGuestAction($pdo, $tableId, $messageType)) {
|
||||
http_response_code(409);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'code' => 'pending_on_kds',
|
||||
'message' => $messageType === 'waiter_call'
|
||||
? 'Kelner został już wezwany i czeka na obsłudze w KDS.'
|
||||
: 'Prośba o rachunek jest już aktywna w KDS.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO guest_action_queue (
|
||||
table_id,
|
||||
|
||||
20
api/message_text_helper.php
Normal file
20
api/message_text_helper.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Normalizuje komunikat kolejki do zwykłego tekstu (wiersze = \n w JSON).
|
||||
* Starsze wpisy HTML (<br>, tagi) są konwertowane na plain text.
|
||||
*/
|
||||
function normalizeQueueMessageText(string $text): string
|
||||
{
|
||||
$text = trim($text);
|
||||
if ($text === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = preg_replace('/<br\s*\/?>/i', "\n", $text) ?? $text;
|
||||
$text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$text = str_replace(["\r\n", "\r"], "\n", $text);
|
||||
$text = preg_replace("/\n{3,}/", "\n\n", $text) ?? $text;
|
||||
|
||||
return trim($text);
|
||||
}
|
||||
@@ -41,11 +41,6 @@
|
||||
<h1 class="logo-text">Karczma Biesiada</h1>
|
||||
<div id="tableLabel" class="table-badge">Wybierz stolik</div>
|
||||
</header>
|
||||
<div style="background: rgba(226, 176, 126, 0.1); border: 1px solid rgba(226, 176, 126, 0.3); border-radius: 12px; padding: 12px; margin-bottom: 24px; text-align: center; font-size: 13px; color: var(--text-muted); line-height: 1.5;">
|
||||
<span style="color: var(--primary); font-weight: 600; display: block; margin-bottom: 4px;">Wdrażamy nową aplikację! 🚀</span>
|
||||
Testujemy właśnie nowe funkcje. Niebawem z tego miejsca będziesz mógł wezwać obsługę lub poprosić o rachunek.
|
||||
</div>
|
||||
|
||||
<!-- <div id="greetingBanner"
|
||||
style="display:none; text-align:center; padding: 10px; font-weight:600; color:var(--primary); font-family:'Playfair Display', serif; font-size:18px;">
|
||||
</div> -->
|
||||
@@ -146,10 +141,10 @@
|
||||
<span class="nav-icon">📖</span>
|
||||
<span class="nav-label">Menu</span>
|
||||
</div>
|
||||
<!-- <div class="nav-item action-call" onclick="openWaiterDialog()">
|
||||
<div class="nav-item action-call" onclick="openWaiterDialog()">
|
||||
<span class="nav-icon">🛎️</span>
|
||||
<span class="nav-label">Kelner</span>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="nav-item action-bill" onclick="openBillDialog()">
|
||||
<span class="nav-icon">💳</span>
|
||||
<span class="nav-label">Rachunek</span>
|
||||
@@ -245,7 +240,7 @@
|
||||
<div style="display:flex; gap:12px;">
|
||||
<button class="btn btn-secondary" style="flex:1;" onclick="goBackToBillList()"
|
||||
id="btnBackToBills">Wróć</button>
|
||||
<!-- <button class="btn btn-primary" style="flex:2;" onclick="goToStep('stepPayment')">Poproś rachunek</button> -->
|
||||
<button class="btn btn-primary" style="flex:2;" onclick="proceedToBillPayment()">Poproś rachunek</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -871,6 +871,16 @@ header {
|
||||
filter: grayscale(0) opacity(1);
|
||||
}
|
||||
|
||||
.nav-item.nav-action-pending {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.nav-item.nav-action-pending .nav-label::after {
|
||||
content: " · w toku";
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-item:active .nav-icon {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
@@ -103,6 +103,12 @@ function trackEvent(eventName, payload = {}) {
|
||||
}
|
||||
|
||||
let cachedOpenBills = [];
|
||||
let guestPendingActions = {
|
||||
waiter_call: false,
|
||||
bill_request: false,
|
||||
};
|
||||
let guestPendingPollTimer = null;
|
||||
const GUEST_PENDING_POLL_MS = 15000;
|
||||
|
||||
function cacheOpenBills(bills) {
|
||||
cachedOpenBills = Array.isArray(bills) ? bills : [];
|
||||
@@ -140,7 +146,114 @@ async function prefetchOpenBills() {
|
||||
}
|
||||
}
|
||||
|
||||
function queueGuestAction(messageType, messageText, extra = {}) {
|
||||
/**
|
||||
* Komunikat do kolejki KDS — zwykły tekst, wiersze oddzielone \n (w JSON jako entery).
|
||||
* @param {string} title
|
||||
* @param {{ label?: string, value?: string }[]} lines
|
||||
*/
|
||||
function formatGuestQueueMessage(title, lines = []) {
|
||||
const rows = (Array.isArray(lines) ? lines : [])
|
||||
.map((line) => {
|
||||
const label = String(line?.label ?? "").trim();
|
||||
const value = String(line?.value ?? "").trim();
|
||||
if (!label && !value) return "";
|
||||
if (label && value) return `${label} ${value}`;
|
||||
return label || value;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (!rows.length) {
|
||||
return title;
|
||||
}
|
||||
|
||||
return `${title}\n${rows.join("\n")}`;
|
||||
}
|
||||
|
||||
function buildWaiterCallQueueMessage() {
|
||||
return "Przywołanie kelnera";
|
||||
}
|
||||
|
||||
function buildBillRequestQueueMessage(docType) {
|
||||
const lines = [
|
||||
{ label: "Forma płatności:", value: billState.payment || "nieznana" },
|
||||
{ label: "Dokument:", value: docType === "faktura" ? "faktura" : "paragon" },
|
||||
];
|
||||
|
||||
if (docType === "faktura") {
|
||||
lines.push({ label: "NIP:", value: billState.nip || "—" });
|
||||
lines.push({ label: "Firma:", value: billState.company?.name || "—" });
|
||||
const addressParts = [
|
||||
billState.company?.street,
|
||||
[billState.company?.zip, billState.company?.city].filter(Boolean).join(" "),
|
||||
].filter(Boolean);
|
||||
if (addressParts.length) {
|
||||
lines.push({ label: "Adres:", value: addressParts.join(", ") });
|
||||
}
|
||||
}
|
||||
|
||||
return formatGuestQueueMessage("Prośba o rachunek", lines);
|
||||
}
|
||||
|
||||
function guestActionBlockedMessage(messageType) {
|
||||
if (messageType === "waiter_call") {
|
||||
return "Kelner został już wezwany. Poczekaj, aż obsługa potwierdzi zgłoszenie na panelu.";
|
||||
}
|
||||
return "Prośba o rachunek została już wysłana. Poczekaj, aż obsługa ją obsłuży.";
|
||||
}
|
||||
|
||||
function updateGuestActionNavState() {
|
||||
const waiterNav = document.querySelector(".bottom-nav .action-call");
|
||||
const billNav = document.querySelector(".bottom-nav .action-bill");
|
||||
if (waiterNav) {
|
||||
waiterNav.classList.toggle("nav-action-pending", guestPendingActions.waiter_call);
|
||||
}
|
||||
if (billNav) {
|
||||
billNav.classList.toggle("nav-action-pending", guestPendingActions.bill_request);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshGuestPendingActions() {
|
||||
if (!hashParam && !tableParam) {
|
||||
return guestPendingActions;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (hashParam) params.set("h", hashParam);
|
||||
if (tableParam) params.set("tableId", tableParam);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${guestActionQueueEndpoint}?${params.toString()}`);
|
||||
const result = await res.json();
|
||||
if (result.status === "success" && result.pending) {
|
||||
guestPendingActions.waiter_call = !!result.pending.waiter_call;
|
||||
guestPendingActions.bill_request = !!result.pending.bill_request;
|
||||
updateGuestActionNavState();
|
||||
}
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
|
||||
return guestPendingActions;
|
||||
}
|
||||
|
||||
function startGuestPendingPoll() {
|
||||
if (guestPendingPollTimer) return;
|
||||
guestPendingPollTimer = setInterval(() => {
|
||||
if (!hashParam && !tableParam) return;
|
||||
refreshGuestPendingActions();
|
||||
}, GUEST_PENDING_POLL_MS);
|
||||
}
|
||||
|
||||
async function ensureGuestActionAllowed(messageType) {
|
||||
await refreshGuestPendingActions();
|
||||
if (!guestPendingActions[messageType]) {
|
||||
return true;
|
||||
}
|
||||
showToast(guestActionBlockedMessage(messageType));
|
||||
return false;
|
||||
}
|
||||
|
||||
async function queueGuestAction(messageType, messageText, extra = {}) {
|
||||
const operator = getQueueOperatorFields();
|
||||
const body = {
|
||||
tableId: tableParam || null,
|
||||
@@ -152,14 +265,31 @@ function queueGuestAction(messageType, messageText, extra = {}) {
|
||||
extra,
|
||||
};
|
||||
|
||||
fetch(guestActionQueueEndpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
keepalive: true
|
||||
}).catch(() => {
|
||||
// best effort - ignore queue errors in UI
|
||||
});
|
||||
try {
|
||||
const res = await fetch(guestActionQueueEndpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
keepalive: true,
|
||||
});
|
||||
const result = await res.json().catch(() => ({}));
|
||||
|
||||
if (res.status === 409 || result.code === "pending_on_kds") {
|
||||
guestPendingActions[messageType] = true;
|
||||
updateGuestActionNavState();
|
||||
return { ok: false, reason: "pending" };
|
||||
}
|
||||
|
||||
if (!res.ok || result.status !== "success") {
|
||||
return { ok: false, reason: "error" };
|
||||
}
|
||||
|
||||
guestPendingActions[messageType] = true;
|
||||
updateGuestActionNavState();
|
||||
return { ok: true };
|
||||
} catch {
|
||||
return { ok: false, reason: "error" };
|
||||
}
|
||||
}
|
||||
|
||||
if (hashParam) {
|
||||
@@ -606,6 +736,8 @@ async function fetchOrders() {
|
||||
if (result.tableName && result.tableName !== '') {
|
||||
tableLabel.textContent = result.tableName.toUpperCase().startsWith("STOLIK") ? result.tableName : `Stolik ${result.tableName}`;
|
||||
tableParam = result.tableName; // Aktualizacja do właściwej nazwy na poczet innych zapytań
|
||||
refreshGuestPendingActions();
|
||||
startGuestPendingPoll();
|
||||
}
|
||||
|
||||
// API teraz samo filtruje i zwraca tylko to co nas interesuje (za pomocą mocnego wyrażenia regularnego)
|
||||
@@ -678,16 +810,35 @@ function sendApiSimulated(actionName, details) {
|
||||
// }
|
||||
}
|
||||
|
||||
window.callWaiter = function (type) {
|
||||
if (type === 'order') {
|
||||
trackEvent("waiter_call_requested", { waiterType: "order" });
|
||||
queueGuestAction("waiter_call", "Przywołanie kelnera", { waiterType: "order" });
|
||||
sendApiSimulated("CallWaiter_Order", { table: tableParam });
|
||||
showToast("Kelner wkrótce do Ciebie podejdzie!");
|
||||
window.callWaiter = async function (type) {
|
||||
if (type !== "order") return;
|
||||
|
||||
if (!(await ensureGuestActionAllowed("waiter_call"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queued = await queueGuestAction("waiter_call", buildWaiterCallQueueMessage(), {
|
||||
waiterType: "order",
|
||||
});
|
||||
|
||||
if (!queued.ok) {
|
||||
if (queued.reason === "pending") {
|
||||
showToast(guestActionBlockedMessage("waiter_call"));
|
||||
} else {
|
||||
showToast("Nie udało się wysłać wezwania. Spróbuj ponownie za chwilę.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
trackEvent("waiter_call_requested", { waiterType: "order" });
|
||||
sendApiSimulated("CallWaiter_Order", { table: tableParam });
|
||||
showToast("Kelner wkrótce do Ciebie podejdzie!");
|
||||
};
|
||||
|
||||
window.openWaiterDialog = function () {
|
||||
window.openWaiterDialog = async function () {
|
||||
if (!(await ensureGuestActionAllowed("waiter_call"))) {
|
||||
return;
|
||||
}
|
||||
document.getElementById("waiterModal").classList.add("active");
|
||||
document.body.style.overflow = 'hidden';
|
||||
};
|
||||
@@ -697,12 +848,20 @@ window.closeWaiterDialog = function () {
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
|
||||
window.confirmCallWaiter = function () {
|
||||
window.confirmCallWaiter = async function () {
|
||||
closeWaiterDialog();
|
||||
callWaiter('order');
|
||||
await callWaiter("order");
|
||||
};
|
||||
|
||||
window.proceedToBillPayment = async function () {
|
||||
if (!(await ensureGuestActionAllowed("bill_request"))) {
|
||||
return;
|
||||
}
|
||||
goToStep("stepPayment");
|
||||
};
|
||||
|
||||
window.openBillDialog = async function () {
|
||||
await refreshGuestPendingActions();
|
||||
trackEvent("bill_dialog_opened");
|
||||
billState = { payment: '', doc: '', nip: '', company: null, selectedBillId: null };
|
||||
document.getElementById("billModal").classList.add("active");
|
||||
@@ -900,15 +1059,25 @@ window.selectPayment = function (method) {
|
||||
goToStep("stepDocument");
|
||||
};
|
||||
|
||||
window.selectDocument = function (docType) {
|
||||
window.selectDocument = async function (docType) {
|
||||
billState.doc = docType;
|
||||
if (docType === 'paragon') {
|
||||
trackEvent("bill_request_sent", { docType: "paragon" });
|
||||
const queueMessage = `Prośba o rachunek | forma płatności: ${billState.payment || "nieznana"} | dokument: paragon`;
|
||||
queueGuestAction("bill_request", queueMessage, {
|
||||
if (!(await ensureGuestActionAllowed("bill_request"))) {
|
||||
return;
|
||||
}
|
||||
const queued = await queueGuestAction("bill_request", buildBillRequestQueueMessage("paragon"), {
|
||||
payment: billState.payment || null,
|
||||
docType: "paragon"
|
||||
docType: "paragon",
|
||||
});
|
||||
if (!queued.ok) {
|
||||
if (queued.reason === "pending") {
|
||||
showToast(guestActionBlockedMessage("bill_request"));
|
||||
} else {
|
||||
showToast("Nie udało się wysłać prośby o rachunek. Spróbuj ponownie za chwilę.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
trackEvent("bill_request_sent", { docType: "paragon" });
|
||||
closeBillDialog();
|
||||
sendApiSimulated("CallWaiter_Bill", { table: tableParam, billId: billState.selectedBillId, payment: billState.payment, doc: 'paragon' });
|
||||
showToast("Kelner przyniesie paragon do opłacenia!");
|
||||
@@ -1084,21 +1253,34 @@ window.editCompanyData = function () {
|
||||
}
|
||||
};
|
||||
|
||||
window.confirmInvoice = function () {
|
||||
window.confirmInvoice = async function () {
|
||||
billState.company.name = document.getElementById("cmpName").value;
|
||||
billState.company.street = document.getElementById("cmpStreet").value;
|
||||
billState.company.zip = document.getElementById("cmpZip").value;
|
||||
billState.company.city = document.getElementById("cmpCity").value;
|
||||
|
||||
closeBillDialog();
|
||||
trackEvent("bill_request_sent", { docType: "faktura" });
|
||||
const queueMessage = `Prośba o rachunek | forma płatności: ${billState.payment || "nieznana"} | dokument: faktura | NIP: ${billState.nip || "-"} | firma: ${billState.company?.name || "-"}`;
|
||||
queueGuestAction("bill_request", queueMessage, {
|
||||
if (!(await ensureGuestActionAllowed("bill_request"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queued = await queueGuestAction("bill_request", buildBillRequestQueueMessage("faktura"), {
|
||||
payment: billState.payment || null,
|
||||
docType: "faktura",
|
||||
nip: billState.nip || null,
|
||||
company: billState.company || null
|
||||
company: billState.company || null,
|
||||
});
|
||||
|
||||
if (!queued.ok) {
|
||||
if (queued.reason === "pending") {
|
||||
showToast(guestActionBlockedMessage("bill_request"));
|
||||
} else {
|
||||
showToast("Nie udało się wysłać prośby o rachunek. Spróbuj ponownie za chwilę.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
closeBillDialog();
|
||||
trackEvent("bill_request_sent", { docType: "faktura" });
|
||||
sendApiSimulated("CallWaiter_Bill", {
|
||||
table: tableParam,
|
||||
billId: billState.selectedBillId,
|
||||
@@ -1137,6 +1319,8 @@ function startApp() {
|
||||
initUserProfile();
|
||||
fetchOrders();
|
||||
prefetchOpenBills();
|
||||
refreshGuestPendingActions();
|
||||
startGuestPendingPoll();
|
||||
if (!window.ordersInterval) {
|
||||
window.ordersInterval = setInterval(fetchOrders, 10000);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ requireAdminAuth(true);
|
||||
.map(v => String(v || "").trim())
|
||||
.filter(Boolean)
|
||||
.join(" ") || "-";
|
||||
return `<tr><td>${row.id}</td><td>${when}</td><td>${row.table_id || "-"}</td><td>${kelner}</td><td>${typeLabel}</td><td>${messageText}</td><td>${apiSent}</td><td>${kdsDone}</td></tr>`;
|
||||
return `<tr><td>${row.id}</td><td>${when}</td><td>${row.table_id || "-"}</td><td>${kelner}</td><td>${typeLabel}</td><td style="white-space:pre-line;max-width:360px">${messageText}</td><td>${apiSent}</td><td>${kdsDone}</td></tr>`;
|
||||
}).join("")
|
||||
: `<tr><td colspan="8" class="muted">Brak danych</td></tr>`;
|
||||
} catch (e) {
|
||||
|
||||
@@ -292,6 +292,7 @@ requireAdminAuth(true);
|
||||
font-size: 0.9rem;
|
||||
color: #cbd5e1;
|
||||
line-height: 1.4;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.guest-alert-time {
|
||||
|
||||
@@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS guest_action_queue (
|
||||
table_id VARCHAR(32) NOT NULL,
|
||||
message_type ENUM('waiter_call', 'bill_request') NOT NULL,
|
||||
message_text TEXT NOT NULL,
|
||||
message_format ENUM('plain', 'html') NOT NULL DEFAULT 'plain' COMMENT 'deprecated — tylko plain; kolumna do usunięcia',
|
||||
otwierajacy_imie VARCHAR(100) NULL,
|
||||
otwierajacy_nazwisko VARCHAR(100) NULL,
|
||||
api_sent TINYINT(1) NOT NULL DEFAULT 0,
|
||||
|
||||
2
scripts/guest_action_queue_add_message_format.sql
Normal file
2
scripts/guest_action_queue_add_message_format.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE guest_action_queue
|
||||
ADD COLUMN message_format ENUM('plain', 'html') NOT NULL DEFAULT 'plain' AFTER message_text;
|
||||
14
scripts/migrate_guest_action_queue_message_format.php
Normal file
14
scripts/migrate_guest_action_queue_message_format.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = getAnalyticsPdo();
|
||||
|
||||
$columns = $pdo->query("SHOW COLUMNS FROM guest_action_queue LIKE 'message_format'")->fetch();
|
||||
if ($columns) {
|
||||
echo "Kolumna message_format już istnieje.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$sql = file_get_contents(__DIR__ . '/guest_action_queue_add_message_format.sql');
|
||||
$pdo->exec($sql);
|
||||
echo "Dodano kolumnę message_format.\n";
|
||||
@@ -38,14 +38,14 @@ $samples = [
|
||||
[
|
||||
'table_id' => '12',
|
||||
'message_type' => 'waiter_call',
|
||||
'message_text' => '[DEMO] Przywołanie kelnera',
|
||||
'message_text' => "[DEMO] Przywołanie kelnera",
|
||||
'otwierajacy_imie' => 'Jan',
|
||||
'otwierajacy_nazwisko' => 'Kowalski',
|
||||
],
|
||||
[
|
||||
'table_id' => 'taras 5',
|
||||
'message_type' => 'bill_request',
|
||||
'message_text' => '[DEMO] Prośba o rachunek | forma płatności: karta | dokument: paragon',
|
||||
'message_text' => "[DEMO] Prośba o rachunek\nForma płatności: karta\nDokument: paragon",
|
||||
'otwierajacy_imie' => 'Anna',
|
||||
'otwierajacy_nazwisko' => 'Nowak',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user