pos_kitchen_printer/models/pos_printer.py

104 lines
4.0 KiB
Python

import socket
import logging
import threading
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class PosPrinter(models.Model):
_inherit = 'pos.printer'
printer_type = fields.Selection(
selection_add=[('network_escpos', 'Network ESC/POS Printer')],
ondelete={'network_escpos': 'set default'}
)
network_printer_ip = fields.Char(
string='Network Printer IP Address',
help="IP address of the generic ESC/POS network printer.",
default="0.0.0.0"
)
@api.model
def _load_pos_data_fields(self, config_id):
fields = super()._load_pos_data_fields(config_id)
if 'network_printer_ip' not in fields:
fields.append('network_printer_ip')
return fields
@api.model
def print_network_kitchen_receipt(self, printer_id, receipt_data):
"""
Receives structural receipt_data from POS frontend and sends ESC/POS string to network printer.
Optimized to use background threading to prevent blocking the POS transaction/RPC.
"""
printer = self.browse(printer_id)
if not printer or not printer.network_printer_ip:
return {'successful': False, 'message': 'Printer IP not configured'}
# Start printing in a background thread to avoid POS timeout
# We don't need a new cursor here as we've already browsed the printer
# and we don't write to the database in the thread.
thread = threading.Thread(target=self._threaded_print, args=(printer.network_printer_ip, receipt_data))
thread.daemon = True # Ensure thread doesn't block server shutdown
thread.start()
return {'successful': True, 'message': 'Print job sent to background queue'}
def _threaded_print(self, ip, receipt_data):
"""Perform the actual socket communication in the background."""
try:
# Build basic ESC/POS commands
ESC = b'\x1b'
GS = b'\x1d'
INIT = ESC + b'@'
CUT = GS + b'V\x41\x00'
BOLD_ON = ESC + b'E\x01'
BOLD_OFF = ESC + b'E\x00'
SIZE_DOUBLE = GS + b'!\x11'
SIZE_NORMAL = GS + b'!\x00'
ALIGN_CENTER = ESC + b'a\x01'
ALIGN_LEFT = ESC + b'a\x00'
NEWLINE = b'\n'
data = INIT
data += ALIGN_CENTER + SIZE_DOUBLE + BOLD_ON
data += b"KITCHEN RECEIPT" + NEWLINE
data += SIZE_NORMAL + BOLD_OFF + NEWLINE
if receipt_data.get('table'):
data += ALIGN_LEFT + SIZE_DOUBLE
data += f"Table: {receipt_data['table']}".encode('utf-8') + NEWLINE
data += SIZE_NORMAL
data += ALIGN_LEFT
data += f"Order: {receipt_data.get('order_name', '')}".encode('utf-8') + NEWLINE
data += f"Date: {receipt_data.get('order_time', '')}".encode('utf-8') + NEWLINE
if receipt_data.get('waiter'):
data += f"Waiter: {receipt_data['waiter']}".encode('utf-8') + NEWLINE
data += b"-" * 32 + NEWLINE
for line in receipt_data.get('lines', []):
qty = line.get('qty', 1)
name = line.get('name', '')
note = line.get('note', '')
data += SIZE_DOUBLE
data += f"{qty} x {name}".encode('utf-8') + NEWLINE
data += SIZE_NORMAL
if note:
data += f" NOTE: {note}".encode('utf-8') + NEWLINE
data += NEWLINE
data += b"-" * 32 + NEWLINE + NEWLINE + NEWLINE + CUT
# Send to printer
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5.0) # Slightly longer timeout for background print
s.connect((ip, 9100))
s.sendall(data)
s.close()
_logger.info("Successfully printed to kitchen printer at %s", ip)
except Exception as e:
_logger.error("Background kitchen print failed for IP %s: %s", ip, e)