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_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
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);
|
||||
// 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();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
$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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user