improving printing 2
This commit is contained in:
parent
5deb44b9f8
commit
b0479e299c
@ -89,7 +89,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let currentCustomer = null;
|
||||
|
||||
const paymentModalEl = document.getElementById('paymentSelectionModal');
|
||||
const paymentSelectionModal = paymentModalEl ? new bootstrap.Modal(paymentModalEl) : null;
|
||||
const paymentSelectionModal = paymentModalEl ? new bootstrap.Modal(paymentSelectionModal) : null;
|
||||
const paymentMethodsContainer = document.getElementById('payment-methods-container');
|
||||
|
||||
const productSearchInput = document.getElementById('product-search');
|
||||
@ -698,13 +698,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
customer_id: selectedCustomerId ? selectedCustomerId.value : null,
|
||||
outlet_id: CURRENT_OUTLET ? CURRENT_OUTLET.id : 1,
|
||||
payment_type_id: paymentTypeId,
|
||||
total_amount: isLoyaltyRedemption ? 0 : (subtotal + totalVat), // If loyalty redeemed, total is 0 for this transaction
|
||||
total_amount: isLoyaltyRedemption ? 0 : (subtotal + totalVat),
|
||||
vat: isLoyaltyRedemption ? 0 : totalVat,
|
||||
items: itemsData,
|
||||
redeem_loyalty: isLoyaltyRedemption
|
||||
};
|
||||
|
||||
// Prepare receipt data before clearing cart
|
||||
const receiptData = {
|
||||
orderId: null,
|
||||
customer: currentCustomer ? { name: currentCustomer.name, phone: currentCustomer.phone, address: currentCustomer.address || '' } : null,
|
||||
@ -712,7 +711,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
name: item.name,
|
||||
variant_name: item.variant_name,
|
||||
quantity: item.quantity,
|
||||
price: isLoyaltyRedemption ? 0 : item.price, // If loyalty redeemed, price is 0 for this transaction
|
||||
price: isLoyaltyRedemption ? 0 : item.price,
|
||||
vat_percent: isLoyaltyRedemption ? 0 : item.vat_percent,
|
||||
vat_amount: isLoyaltyRedemption ? 0 : ((item.price * item.quantity) * (item.vat_percent / 100))
|
||||
})),
|
||||
@ -726,6 +725,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
loyaltyRedeemed: isLoyaltyRedemption
|
||||
};
|
||||
|
||||
// Clear UI immediately for responsiveness
|
||||
const tempOrderId = currentOrderId;
|
||||
clearCart();
|
||||
if (paymentSelectionModal) paymentSelectionModal.hide();
|
||||
if (clearCustomerBtn) clearCustomerBtn.click();
|
||||
|
||||
fetch('api/order.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
@ -734,35 +739,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// --- PRINTING LOGIC ---
|
||||
const cashierPrinterIp = CURRENT_OUTLET.cashier_printer_ip;
|
||||
const isLocalCashierIp = cashierPrinterIp && /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(cashierPrinterIp);
|
||||
const kitchenPrinterIp = CURRENT_OUTLET.kitchen_printer_ip;
|
||||
|
||||
// Show browser receipt ONLY IF:
|
||||
// 1. No cashier printer is configured
|
||||
// 2. OR the configured cashier printer is a local IP (which we can't reach from cloud)
|
||||
// This prevents duplicate printing when a valid network printer is used.
|
||||
const isLocalCashierIp = cashierPrinterIp && /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(cashierPrinterIp);
|
||||
const isLocalKitchenIp = kitchenPrinterIp && /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(kitchenPrinterIp);
|
||||
|
||||
// 1. Browser Cashier Print (if needed)
|
||||
if (!cashierPrinterIp || isLocalCashierIp) {
|
||||
printThermalReceipt(receiptData);
|
||||
} else {
|
||||
// Only try network printing if it's NOT a local IP
|
||||
// (prevents useless 1s timeout for cashier print if we already browser-printed)
|
||||
triggerNetworkPrint(data.order_id, 'cashier');
|
||||
}
|
||||
|
||||
// Kitchen is usually always a network printer, so we always try it
|
||||
triggerNetworkPrint(data.order_id, 'kitchen');
|
||||
// 2. Kitchen Network Print (ONLY if NOT local IP)
|
||||
// If it's local, server can't reach it anyway, so skip the fetch to api/print.php
|
||||
if (kitchenPrinterIp && !isLocalKitchenIp) {
|
||||
triggerNetworkPrint(data.order_id, 'kitchen');
|
||||
}
|
||||
|
||||
showToast(`${_t('order_placed')} #${data.order_id}`, 'success');
|
||||
clearCart();
|
||||
if (paymentSelectionModal) paymentSelectionModal.hide();
|
||||
if (clearCustomerBtn) clearCustomerBtn.click();
|
||||
} else showToast(data.error, 'danger');
|
||||
} else {
|
||||
showToast(data.error, 'danger');
|
||||
// Optional: should we restore the cart if it failed?
|
||||
// For now, let's keep it cleared but show the error.
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.triggerNetworkPrint = function(orderId, type) {
|
||||
if (!orderId) return;
|
||||
|
||||
// Check if printer IP is configured for this outlet
|
||||
const printerIp = (type === 'kitchen') ? CURRENT_OUTLET.kitchen_printer_ip : CURRENT_OUTLET.cashier_printer_ip;
|
||||
if (!printerIp) return;
|
||||
|
||||
@ -774,25 +780,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
// Skip toast for local IPs as we know they are unreachable from cloud
|
||||
const isLocalIp = /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(printerIp);
|
||||
if (!isLocalIp) {
|
||||
showToast(`Printer Error (${type}): ${data.error}`, 'warning');
|
||||
} else {
|
||||
console.warn(`Printer Error (${type}) for local IP ${printerIp}: ${data.error}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => console.error(`Network Print Fetch Error (${type}):`, err));
|
||||
};
|
||||
|
||||
/**
|
||||
* Prints a thermal receipt using a hidden iframe for a smoother experience.
|
||||
* To achieve completely silent printing (Direct Print), run Chrome with:
|
||||
* chrome.exe --kiosk-printing --kiosk
|
||||
*/
|
||||
window.printThermalReceipt = function(data) {
|
||||
// Create or get the hidden iframe
|
||||
let iframe = document.getElementById('print-iframe');
|
||||
if (!iframe) {
|
||||
iframe = document.createElement('iframe');
|
||||
@ -849,7 +846,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const loyaltyHtml = data.loyaltyRedeemed ? `<div style="color: #d63384; font-weight: bold; margin: 5px 0; text-align: center;">* Loyalty Reward Applied *</div>` : '';
|
||||
|
||||
const logoHtml = settings.logo_url ? `<img src="${BASE_URL}${settings.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
|
||||
// We skip logo in receipt for absolute speed unless it's already cached.
|
||||
// If users really want the logo, we can re-enable it.
|
||||
const logoHtml = ''; // settings.logo_url ? `<img src="${BASE_URL}${settings.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
|
||||
|
||||
const vatRate = settings.vat_rate || 0;
|
||||
|
||||
@ -927,19 +926,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
doc.write(html);
|
||||
doc.close();
|
||||
|
||||
// Wait for resources (like logo) to load before printing
|
||||
iframe.contentWindow.onload = function() {
|
||||
iframe.contentWindow.focus();
|
||||
iframe.contentWindow.print();
|
||||
};
|
||||
|
||||
// Fallback if onload doesn't fire correctly
|
||||
setTimeout(() => {
|
||||
if (doc.readyState === 'complete') {
|
||||
iframe.contentWindow.focus();
|
||||
iframe.contentWindow.print();
|
||||
}
|
||||
}, 1000);
|
||||
// Print immediately without waiting for resources
|
||||
iframe.contentWindow.focus();
|
||||
iframe.contentWindow.print();
|
||||
};
|
||||
|
||||
window.openRatingQRModal = function() {
|
||||
@ -952,4 +941,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const modal = new bootstrap.Modal(document.getElementById('qrRatingModal'));
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
});
|
||||
@ -14,10 +14,15 @@ class PrinterService {
|
||||
return ['success' => false, 'error' => 'Printer IP is not configured.'];
|
||||
}
|
||||
|
||||
// Determine if IP is local/private. If so, use a very short timeout
|
||||
// because cloud servers cannot reach local IPs anyway.
|
||||
// Determine if IP is local/private.
|
||||
// Cloud servers cannot reach local network IPs (e.g., 192.168.x.x).
|
||||
// If it's a local IP, we return immediately to avoid the 1-second fsockopen timeout.
|
||||
$isLocalIp = preg_match('/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/', $ip);
|
||||
$timeout = $isLocalIp ? 1 : 5; // 1 second for local, 5 for public
|
||||
if ($isLocalIp) {
|
||||
return ['success' => false, 'error' => "Cannot connect to local network IP $ip from cloud. Please use a public IP or a local print proxy."];
|
||||
}
|
||||
|
||||
$timeout = 5; // 5 seconds for public IPs
|
||||
|
||||
try {
|
||||
$fp = @fsockopen($ip, $port, $errno, $errstr, $timeout);
|
||||
@ -36,7 +41,6 @@ class PrinterService {
|
||||
|
||||
/**
|
||||
* Generate basic ESC/POS receipt content.
|
||||
* This is a simplified version. For full features, an ESC/POS library is recommended.
|
||||
*/
|
||||
public static function formatReceipt($order, $items, $company) {
|
||||
$esc = "\x1b";
|
||||
@ -44,30 +48,35 @@ class PrinterService {
|
||||
$line = str_repeat("-", 32) . "\n";
|
||||
|
||||
$out = "";
|
||||
$out .= $esc . "@"; // Initialize printer
|
||||
$out .= $esc . "!" . "\x38"; // Double height and width
|
||||
$out .= $esc . "a" . "\x01"; // Center align
|
||||
$out .= $company['company_name'] . "\n";
|
||||
$out .= ($company['company_name'] ?? 'Restaurant') . "\n";
|
||||
$out .= $esc . "!" . "\x00"; // Reset
|
||||
$out .= $company['address'] . "\n";
|
||||
$out .= $company['phone'] . "\n\n";
|
||||
$out .= ($company['address'] ?? '') . "\n";
|
||||
$out .= ($company['phone'] ?? '') . "\n\n";
|
||||
|
||||
$out .= $esc . "a" . "\x00"; // Left align
|
||||
$out .= "Order ID: #" . $order['id'] . "\n";
|
||||
$out .= "Date: " . $order['created_at'] . "\n";
|
||||
$out .= "Date: " . ($order['created_at'] ?? date('Y-m-d H:i:s')) . "\n";
|
||||
$out .= $line;
|
||||
|
||||
foreach ($items as $item) {
|
||||
$name = substr($item['name'], 0, 20);
|
||||
$qty = $item['quantity'] . "x";
|
||||
$price = number_format($item['price'], 2);
|
||||
$name = substr(($item['product_name'] ?? $item['name'] ?? 'Item'), 0, 20);
|
||||
$qty = ($item['quantity'] ?? 1) . "x";
|
||||
$price = number_format((float)($item['unit_price'] ?? $item['price'] ?? 0), 2);
|
||||
$out .= sprintf("% -20s %3s %7s\n", $name, $qty, $price);
|
||||
|
||||
if (!empty($item['variant_name'])) {
|
||||
$out .= " (" . $item['variant_name'] . ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
$out .= $line;
|
||||
$out .= sprintf("% -20s %11s\n", "TOTAL", number_format($order['total_amount'], 2));
|
||||
$out .= sprintf("% -20s %11s\n", "TOTAL", number_format((float)($order['total_amount'] ?? 0), 2));
|
||||
$out .= "\n\n\n\n";
|
||||
$out .= $esc . "m"; // Cut paper (optional, depending on printer)
|
||||
$out .= $esc . "m"; // Cut paper
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user