diff --git a/models/pos_printer.py b/models/pos_printer.py index 3123b96..38f7adc 100644 --- a/models/pos_printer.py +++ b/models/pos_printer.py @@ -1,5 +1,6 @@ import socket import logging +import threading from odoo import models, fields, api, _ _logger = logging.getLogger(__name__) @@ -28,72 +29,75 @@ class PosPrinter(models.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. - receipt_data expected format: - { - 'order_name': 'Order 0001', - 'order_time': '2023-10-01 12:00:00', - 'lines': [ { 'name': 'Burger', 'qty': 1, 'note': 'No onions' } ], - 'waiter': 'Mitchell Admin', - 'table': 'T1' - } + 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'} - # 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' + # 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'} - 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 + 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(3.0) - s.connect((printer.network_printer_ip, 9100)) + s.settimeout(5.0) # Slightly longer timeout for background print + s.connect((ip, 9100)) s.sendall(data) s.close() - return {'successful': True} + _logger.info("Successfully printed to kitchen printer at %s", ip) except Exception as e: - _logger.error("Failed to print to network ESC/POS printer %s: %s", printer.network_printer_ip, e) - return {'successful': False, 'message': str(e)} + _logger.error("Background kitchen print failed for IP %s: %s", ip, e)