From 035dfe3dab19ea258d2d5d88c75e203c786e93d4 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Sat, 21 Mar 2026 13:22:54 +0700 Subject: [PATCH] first commit --- .gitignore | 2 + README.md | 15 +++ __init__.py | 1 + __manifest__.py | 17 ++++ models/__init__.py | 1 + models/pos_printer.py | 99 +++++++++++++++++++ .../screens/ticket_screen/ticket_screen.xml | 22 +++++ static/src/app/services/pos_store.js | 67 +++++++++++++ views/pos_printer_views.xml | 13 +++ 9 files changed, 237 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 models/__init__.py create mode 100644 models/pos_printer.py create mode 100644 static/src/app/screens/ticket_screen/ticket_screen.xml create mode 100644 static/src/app/services/pos_store.js create mode 100644 views/pos_printer_views.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d646835 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2717fb --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# POS Kitchen ESC/POS Network Printer + +This module adds support for generic network ESC/POS thermal printers in the Point of Sale Kitchen Display / Printing configurations. + +## Features +- Add basic raw network printing over IP using port 9100 without relying on IoT box or EPSON-only support. +- Configurable per-printer IP address directly under Point of Sale -> Printers settings. +- Formats standard kitchen receipt containing products, quantity, and internal notes (without prices). +- Replaces/upgrades the POS ticket screen's existing hidden reprint functionality with highly visible 'Reprint Kitchen Checker' buttons. + +## Setup +- Go to POS Configuration -> Printers +- Add a new "Network ESC/POS Printer". +- Fill in the appropriate local IP address (e.g. `192.168.1.100`) and the POS categories the printer should handle. +- Set your POS configuration to route kitchen printing to this new printer as usual. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..289b0d7 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'POS Kitchen ESC/POS Network Printer', + 'version': '1.0', + 'category': 'Sales/Point of Sale', + 'summary': 'Support generic network (IP) ESC/POS printers for kitchen receipts', + 'depends': ['point_of_sale'], + 'data': [ + 'views/pos_printer_views.xml', + ], + 'assets': { + 'point_of_sale._assets_pos': [ + 'pos_kitchen_printer/static/src/**/*', + ], + }, + 'installable': True, + 'license': 'LGPL-3', +} diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..821e230 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +from . import pos_printer diff --git a/models/pos_printer.py b/models/pos_printer.py new file mode 100644 index 0000000..3123b96 --- /dev/null +++ b/models/pos_printer.py @@ -0,0 +1,99 @@ +import socket +import logging +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. + 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' + } + """ + 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' + + 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 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(3.0) + s.connect((printer.network_printer_ip, 9100)) + s.sendall(data) + s.close() + return {'successful': True} + 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)} diff --git a/static/src/app/screens/ticket_screen/ticket_screen.xml b/static/src/app/screens/ticket_screen/ticket_screen.xml new file mode 100644 index 0000000..3e42e31 --- /dev/null +++ b/static/src/app/screens/ticket_screen/ticket_screen.xml @@ -0,0 +1,22 @@ + + + + + + + + + + +