adding printers
This commit is contained in:
parent
a7bd1b142a
commit
16f05ef073
@ -13,6 +13,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|||||||
$name = trim($_POST['name']);
|
$name = trim($_POST['name']);
|
||||||
$name_ar = trim($_POST['name_ar'] ?? '');
|
$name_ar = trim($_POST['name_ar'] ?? '');
|
||||||
$address = trim($_POST['address']);
|
$address = trim($_POST['address']);
|
||||||
|
$cashier_printer_ip = trim($_POST['cashier_printer_ip'] ?? '');
|
||||||
|
$kitchen_printer_ip = trim($_POST['kitchen_printer_ip'] ?? '');
|
||||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : null;
|
$id = isset($_POST['id']) ? (int)$_POST['id'] : null;
|
||||||
|
|
||||||
if (empty($name)) {
|
if (empty($name)) {
|
||||||
@ -23,16 +25,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|||||||
if (!has_permission('outlets_add')) {
|
if (!has_permission('outlets_add')) {
|
||||||
$message = '<div class="alert alert-danger">Access Denied.</div>';
|
$message = '<div class="alert alert-danger">Access Denied.</div>';
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("UPDATE outlets SET name = ?, name_ar = ?, address = ? WHERE id = ?");
|
$stmt = $pdo->prepare("UPDATE outlets SET name = ?, name_ar = ?, address = ?, cashier_printer_ip = ?, kitchen_printer_ip = ? WHERE id = ?");
|
||||||
$stmt->execute([$name, $name_ar, $address, $id]);
|
$stmt->execute([$name, $name_ar, $address, $cashier_printer_ip, $kitchen_printer_ip, $id]);
|
||||||
$message = '<div class="alert alert-success">Outlet updated successfully!</div>';
|
$message = '<div class="alert alert-success">Outlet updated successfully!</div>';
|
||||||
}
|
}
|
||||||
} elseif ($action === 'add_outlet') {
|
} elseif ($action === 'add_outlet') {
|
||||||
if (!has_permission('outlets_add')) {
|
if (!has_permission('outlets_add')) {
|
||||||
$message = '<div class="alert alert-danger">Access Denied.</div>';
|
$message = '<div class="alert alert-danger">Access Denied.</div>';
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("INSERT INTO outlets (name, name_ar, address) VALUES (?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO outlets (name, name_ar, address, cashier_printer_ip, kitchen_printer_ip) VALUES (?, ?, ?, ?, ?)");
|
||||||
$stmt->execute([$name, $name_ar, $address]);
|
$stmt->execute([$name, $name_ar, $address, $cashier_printer_ip, $kitchen_printer_ip]);
|
||||||
$message = '<div class="alert alert-success">Outlet created successfully!</div>';
|
$message = '<div class="alert alert-success">Outlet created successfully!</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,8 +95,8 @@ include 'includes/header.php';
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="ps-4">ID</th>
|
<th class="ps-4">ID</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Arabic Name</th>
|
|
||||||
<th>Address</th>
|
<th>Address</th>
|
||||||
|
<th>Printers (Cashier / Kitchen)</th>
|
||||||
<th class="text-end pe-4">Actions</th>
|
<th class="text-end pe-4">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -102,9 +104,19 @@ include 'includes/header.php';
|
|||||||
<?php foreach ($outlets as $outlet): ?>
|
<?php foreach ($outlets as $outlet): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="ps-4 fw-medium">#<?= $outlet['id'] ?></td>
|
<td class="ps-4 fw-medium">#<?= $outlet['id'] ?></td>
|
||||||
<td class="fw-bold"><?= htmlspecialchars($outlet['name']) ?></td>
|
<td>
|
||||||
<td><?= htmlspecialchars($outlet['name_ar'] ?: '-') ?></td>
|
<div class="fw-bold"><?= htmlspecialchars($outlet['name']) ?></div>
|
||||||
|
<small class="text-muted"><?= htmlspecialchars($outlet['name_ar'] ?: '-') ?></small>
|
||||||
|
</td>
|
||||||
<td><small class="text-muted"><?= htmlspecialchars($outlet['address']) ?></small></td>
|
<td><small class="text-muted"><?= htmlspecialchars($outlet['address']) ?></small></td>
|
||||||
|
<td>
|
||||||
|
<div class="small">
|
||||||
|
<i class="bi bi-printer text-primary"></i> <?= htmlspecialchars($outlet['cashier_printer_ip'] ?: 'Not set') ?>
|
||||||
|
</div>
|
||||||
|
<div class="small mt-1">
|
||||||
|
<i class="bi bi-printer text-warning"></i> <?= htmlspecialchars($outlet['kitchen_printer_ip'] ?: 'Not set') ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="text-end pe-4">
|
<td class="text-end pe-4">
|
||||||
<?php if (has_permission('outlets_add')): ?>
|
<?php if (has_permission('outlets_add')): ?>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary me-1"
|
<button type="button" class="btn btn-sm btn-outline-primary me-1"
|
||||||
@ -163,6 +175,16 @@ include 'includes/header.php';
|
|||||||
<label class="form-label">Address</label>
|
<label class="form-label">Address</label>
|
||||||
<textarea name="address" id="outletAddress" class="form-control" rows="3"></textarea>
|
<textarea name="address" id="outletAddress" class="form-control" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h6 class="fw-bold mb-3">IP/TCP Printers (ESC/POS)</h6>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small">Cashier Printer IP</label>
|
||||||
|
<input type="text" name="cashier_printer_ip" id="outletCashierIp" class="form-control" placeholder="e.g. 192.168.1.100">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small">Kitchen Printer IP</label>
|
||||||
|
<input type="text" name="kitchen_printer_ip" id="outletKitchenIp" class="form-control" placeholder="e.g. 192.168.1.101">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
@ -189,6 +211,8 @@ function prepareEditForm(outlet) {
|
|||||||
document.getElementById('outletName').value = outlet.name || '';
|
document.getElementById('outletName').value = outlet.name || '';
|
||||||
document.getElementById('outletNameAr').value = outlet.name_ar || '';
|
document.getElementById('outletNameAr').value = outlet.name_ar || '';
|
||||||
document.getElementById('outletAddress').value = outlet.address || '';
|
document.getElementById('outletAddress').value = outlet.address || '';
|
||||||
|
document.getElementById('outletCashierIp').value = outlet.cashier_printer_ip || '';
|
||||||
|
document.getElementById('outletKitchenIp').value = outlet.kitchen_printer_ip || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('btnTranslate').addEventListener('click', function() {
|
document.getElementById('btnTranslate').addEventListener('click', function() {
|
||||||
|
|||||||
60
api/print.php
Normal file
60
api/print.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
require_once __DIR__ . '/../includes/PrinterService.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$order_id = $input['order_id'] ?? null;
|
||||||
|
$type = $input['type'] ?? 'cashier'; // 'cashier' or 'kitchen'
|
||||||
|
|
||||||
|
if (!$order_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Order ID is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch order details with outlet info
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT o.*, out.cashier_printer_ip, out.kitchen_printer_ip
|
||||||
|
FROM orders o
|
||||||
|
JOIN outlets out ON o.outlet_id = out.id
|
||||||
|
WHERE o.id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$order_id]);
|
||||||
|
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Order not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch items
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM order_items WHERE order_id = ?");
|
||||||
|
$stmt->execute([$order_id]);
|
||||||
|
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch company settings
|
||||||
|
$company = get_company_settings();
|
||||||
|
|
||||||
|
// Determine target IP
|
||||||
|
$printer_ip = ($type === 'kitchen') ? $order['kitchen_printer_ip'] : $order['cashier_printer_ip'];
|
||||||
|
|
||||||
|
if (empty($printer_ip)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => "No $type printer IP configured for this outlet"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate ESC/POS data
|
||||||
|
$data = PrinterService::formatReceipt($order, $items, $company);
|
||||||
|
|
||||||
|
// Send to printer
|
||||||
|
$result = PrinterService::sendToNetworkPrinter($printer_ip, $data);
|
||||||
|
|
||||||
|
echo json_encode($result);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@ -589,6 +589,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(0);
|
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(0);
|
||||||
// Always show VAT row even if 0
|
// Always show VAT row even if 0
|
||||||
if (cartVatRow) cartVatRow.classList.remove('d-none');
|
if (cartVatRow) cartVatRow.classList.remove('d-none');
|
||||||
|
if (cartVatInput) cartVatInput.value = totalVat.toFixed(3);
|
||||||
if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0);
|
if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0);
|
||||||
if (quickOrderBtn) { quickOrderBtn.disabled = true; quickOrderBtn.innerText = _t('quick_pay'); }
|
if (quickOrderBtn) { quickOrderBtn.disabled = true; quickOrderBtn.innerText = _t('quick_pay'); }
|
||||||
if (placeOrderBtn) { placeOrderBtn.disabled = true; placeOrderBtn.innerText = _t('save_bill'); }
|
if (placeOrderBtn) { placeOrderBtn.disabled = true; placeOrderBtn.innerText = _t('save_bill'); }
|
||||||
@ -734,6 +735,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Show receipt for both Quick Pay and Place Order
|
// Show receipt for both Quick Pay and Place Order
|
||||||
printThermalReceipt(receiptData);
|
printThermalReceipt(receiptData);
|
||||||
|
|
||||||
|
// Trigger network printing
|
||||||
|
triggerNetworkPrint(data.order_id, 'cashier');
|
||||||
|
triggerNetworkPrint(data.order_id, 'kitchen');
|
||||||
|
|
||||||
showToast(`${_t('order_placed')} #${data.order_id}`, 'success');
|
showToast(`${_t('order_placed')} #${data.order_id}`, 'success');
|
||||||
clearCart();
|
clearCart();
|
||||||
if (paymentSelectionModal) paymentSelectionModal.hide();
|
if (paymentSelectionModal) paymentSelectionModal.hide();
|
||||||
@ -742,6 +747,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
fetch('api/print.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ order_id: orderId, type: type })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
console.error(`Network Print Error (${type}):`, data.error);
|
||||||
|
// Optionally show a toast for errors, but might be too noisy
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error(`Network Print Fetch Error (${type}):`, err));
|
||||||
|
};
|
||||||
|
|
||||||
window.printThermalReceipt = function(data) {
|
window.printThermalReceipt = function(data) {
|
||||||
const width = 450;
|
const width = 450;
|
||||||
const height = 800;
|
const height = 800;
|
||||||
|
|||||||
3
db/migrations/043_add_printer_ips_to_outlets.sql
Normal file
3
db/migrations/043_add_printer_ips_to_outlets.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Add printer IP columns to outlets table
|
||||||
|
ALTER TABLE outlets ADD COLUMN cashier_printer_ip VARCHAR(45) DEFAULT NULL;
|
||||||
|
ALTER TABLE outlets ADD COLUMN kitchen_printer_ip VARCHAR(45) DEFAULT NULL;
|
||||||
68
includes/PrinterService.php
Normal file
68
includes/PrinterService.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class PrinterService {
|
||||||
|
/**
|
||||||
|
* Send raw ESC/POS data to a network printer.
|
||||||
|
*
|
||||||
|
* @param string $ip Printer IP address
|
||||||
|
* @param string $data Raw ESC/POS data
|
||||||
|
* @param int $port Printer port (default 9100)
|
||||||
|
* @return array Success status and error message if any
|
||||||
|
*/
|
||||||
|
public static function sendToNetworkPrinter($ip, $data, $port = 9100) {
|
||||||
|
if (empty($ip)) {
|
||||||
|
return ['success' => false, 'error' => 'Printer IP is not configured.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$fp = @fsockopen($ip, $port, $errno, $errstr, 5);
|
||||||
|
if (!$fp) {
|
||||||
|
return ['success' => false, 'error' => "Could not connect to printer at $ip:$port. Error: $errstr ($errno)"];
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($fp, $data);
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
return ['success' => true];
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
$gs = "\x1d";
|
||||||
|
$line = str_repeat("-", 32) . "\n";
|
||||||
|
|
||||||
|
$out = "";
|
||||||
|
$out .= $esc . "!" . "\x38"; // Double height and width
|
||||||
|
$out .= $esc . "a" . "\x01"; // Center align
|
||||||
|
$out .= $company['company_name'] . "\n";
|
||||||
|
$out .= $esc . "!" . "\x00"; // Reset
|
||||||
|
$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 .= $line;
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$name = substr($item['name'], 0, 20);
|
||||||
|
$qty = $item['quantity'] . "x";
|
||||||
|
$price = number_format($item['price'], 2);
|
||||||
|
$out .= sprintf("% -20s %3s %7s\n", $name, $qty, $price);
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= $line;
|
||||||
|
$out .= sprintf("% -20s %11s\n", "TOTAL", number_format($order['total_amount'], 2));
|
||||||
|
$out .= "\n\n\n\n";
|
||||||
|
$out .= $esc . "m"; // Cut paper (optional, depending on printer)
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
pos.php
9
pos.php
@ -61,13 +61,16 @@ $table_id = $_GET['table'] ?? '1'; // Default table
|
|||||||
$settings = get_company_settings();
|
$settings = get_company_settings();
|
||||||
$order_type = $_GET['order_type'] ?? 'takeaway';
|
$order_type = $_GET['order_type'] ?? 'takeaway';
|
||||||
|
|
||||||
$current_outlet_name = 'Unknown Outlet';
|
$current_outlet = null;
|
||||||
foreach ($outlets as $o) {
|
foreach ($outlets as $o) {
|
||||||
if ($o['id'] == $outlet_id) {
|
if ($o['id'] == $outlet_id) {
|
||||||
$current_outlet_name = $o['name'];
|
$current_outlet = $o;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!$current_outlet && count($outlets) > 0) {
|
||||||
|
$current_outlet = $outlets[0];
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch Loyalty Settings
|
// Fetch Loyalty Settings
|
||||||
$loyalty_stmt = $pdo->query("SELECT * FROM loyalty_settings WHERE id = 1");
|
$loyalty_stmt = $pdo->query("SELECT * FROM loyalty_settings WHERE id = 1");
|
||||||
@ -462,7 +465,7 @@ $vat_rate = (float)($settings['vat_rate'] ?? 0);
|
|||||||
const PRODUCT_VARIANTS = <?= json_encode($variants_by_product) ?>;
|
const PRODUCT_VARIANTS = <?= json_encode($variants_by_product) ?>;
|
||||||
const PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
|
const PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
|
||||||
const BASE_URL = '<?= get_base_url() ?>';
|
const BASE_URL = '<?= get_base_url() ?>';
|
||||||
const CURRENT_OUTLET = { id: <?= $outlet_id ?>, name: '<?= addslashes($current_outlet_name) ?>' };
|
const CURRENT_OUTLET = <?= json_encode($current_outlet) ?>;
|
||||||
const CURRENT_USER = { id: <?= $currentUser['id'] ?>, name: '<?= addslashes($currentUser['username']) ?>' };
|
const CURRENT_USER = { id: <?= $currentUser['id'] ?>, name: '<?= addslashes($currentUser['username']) ?>' };
|
||||||
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
|
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
|
||||||
const LANG = 'en';
|
const LANG = 'en';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user