adding printers

This commit is contained in:
Flatlogic Bot 2026-02-27 09:26:03 +00:00
parent a7bd1b142a
commit 16f05ef073
6 changed files with 197 additions and 12 deletions

View File

@ -13,6 +13,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$name = trim($_POST['name']);
$name_ar = trim($_POST['name_ar'] ?? '');
$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;
if (empty($name)) {
@ -23,16 +25,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if (!has_permission('outlets_add')) {
$message = '<div class="alert alert-danger">Access Denied.</div>';
} else {
$stmt = $pdo->prepare("UPDATE outlets SET name = ?, name_ar = ?, address = ? WHERE id = ?");
$stmt->execute([$name, $name_ar, $address, $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, $cashier_printer_ip, $kitchen_printer_ip, $id]);
$message = '<div class="alert alert-success">Outlet updated successfully!</div>';
}
} elseif ($action === 'add_outlet') {
if (!has_permission('outlets_add')) {
$message = '<div class="alert alert-danger">Access Denied.</div>';
} else {
$stmt = $pdo->prepare("INSERT INTO outlets (name, name_ar, address) VALUES (?, ?, ?)");
$stmt->execute([$name, $name_ar, $address]);
$stmt = $pdo->prepare("INSERT INTO outlets (name, name_ar, address, cashier_printer_ip, kitchen_printer_ip) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$name, $name_ar, $address, $cashier_printer_ip, $kitchen_printer_ip]);
$message = '<div class="alert alert-success">Outlet created successfully!</div>';
}
}
@ -93,8 +95,8 @@ include 'includes/header.php';
<tr>
<th class="ps-4">ID</th>
<th>Name</th>
<th>Arabic Name</th>
<th>Address</th>
<th>Printers (Cashier / Kitchen)</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
@ -102,9 +104,19 @@ include 'includes/header.php';
<?php foreach ($outlets as $outlet): ?>
<tr>
<td class="ps-4 fw-medium">#<?= $outlet['id'] ?></td>
<td class="fw-bold"><?= htmlspecialchars($outlet['name']) ?></td>
<td><?= htmlspecialchars($outlet['name_ar'] ?: '-') ?></td>
<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>
<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">
<?php if (has_permission('outlets_add')): ?>
<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>
<textarea name="address" id="outletAddress" class="form-control" rows="3"></textarea>
</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 class="modal-footer">
<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('outletNameAr').value = outlet.name_ar || '';
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() {
@ -233,4 +257,4 @@ document.getElementById('btnTranslate').addEventListener('click', function() {
</script>
<?php endif; ?>
<?php include 'includes/footer.php'; ?>
<?php include 'includes/footer.php'; ?>

60
api/print.php Normal file
View 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()]);
}

View File

@ -589,6 +589,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(0);
// Always show VAT row even if 0
if (cartVatRow) cartVatRow.classList.remove('d-none');
if (cartVatInput) cartVatInput.value = totalVat.toFixed(3);
if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0);
if (quickOrderBtn) { quickOrderBtn.disabled = true; quickOrderBtn.innerText = _t('quick_pay'); }
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
printThermalReceipt(receiptData);
// Trigger network printing
triggerNetworkPrint(data.order_id, 'cashier');
triggerNetworkPrint(data.order_id, 'kitchen');
showToast(`${_t('order_placed')} #${data.order_id}`, 'success');
clearCart();
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) {
const width = 450;
const height = 800;
@ -890,4 +917,4 @@ document.addEventListener('DOMContentLoaded', () => {
const modal = new bootstrap.Modal(document.getElementById('qrRatingModal'));
modal.show();
};
});
});

View 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;

View 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;
}
}

View File

@ -61,13 +61,16 @@ $table_id = $_GET['table'] ?? '1'; // Default table
$settings = get_company_settings();
$order_type = $_GET['order_type'] ?? 'takeaway';
$current_outlet_name = 'Unknown Outlet';
$current_outlet = null;
foreach ($outlets as $o) {
if ($o['id'] == $outlet_id) {
$current_outlet_name = $o['name'];
$current_outlet = $o;
break;
}
}
if (!$current_outlet && count($outlets) > 0) {
$current_outlet = $outlets[0];
}
// Fetch Loyalty Settings
$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 PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
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 LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
const LANG = 'en';