diff --git a/Api/formats.py b/Api/formats.py new file mode 100644 index 0000000..7d139b6 --- /dev/null +++ b/Api/formats.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +import sys +import tempfile +from datetime import datetime +from .qr_generator import QRCodeGenerator +from .printer_factory import PrinterFactory + + +def print_bill_format(printer, d, waiter): + printer.set(align='center', bold=False, height=1, width=1) + printer.text(d["shop_name"]+'\n') + printer.text(d["shop_nit"]+'\n') + printer.text(d["shop_address"]+'\n') + printer.set(align='left', bold=False, height=1, width=1) + printer.textln('===============================================') + text = d['state'] + printer.textln(text) + if d['invoice'] and d['invoice']['resolution']: + text = "Resolucion de Facturacion # " + \ + str(d['invoice']['resolution']['resolution_number']) \ + + "\nValida desde " + \ + d['invoice']['resolution']['valid_date_time_from'] + \ + " hasta "+str(d['invoice']['resolution']['valid_date_time_to']) + printer.textln(text) + printer.ln() + text = "Factura #: " + d['invoice']['invoice_number'] + printer.textln(text) + printer.text("Cliente: " + d["party"]+'\n') + printer.text("CC/NIT: " + d["tax_identifier_code"]+'\n') + printer.text("Direccion: " + d["address"]+'\n') + text = 'MESA: ' + str(d['table'] + "\n") + printer.text(text) + printer.textln('===============================================') + printer.ln() + for line in d["lines"]: + if line['type'] != 'title': + text = line['product'] + printer.text(text) + printer.ln() + text = str(line['quantity']) + " " + " $" + \ + line["unit_price"] + "\n" + printer.text(text) + + printer.set(align='right', bold=False, height=1, width=1) + printer.textln('================================================') + text = "Descuento Realizado: "+str(d["total_discount"])+"\n" + printer.text(text) + text = "Propina: "+str(d["total_tip"])+"\n" + printer.text(text) + text = "Total (sin impuestos): "+str(d["untaxed_amount"])+"\n" + printer.text(text) + text = "Impuestos (INC): "+str(d["tax_amount"])+"\n" + printer.text(text) + text = "Total: "+str(d["total"])+"\n" + printer.text(text) + printer.ln() + if 'payments' in d.keys(): + printer.textln("Pagos: ") + printer.textln('================================================') + + for payment in d['payments']: + text = str(payment["statement"])+" $"+str(payment["amount"]) + printer.textln(text) + printer.set(align='center', bold=False, height=1, width=1) + printer.textln('==============================================\n') + if d["fe_cufe"]: + QR = QRCodeGenerator(d["fe_cufe"]).generate_qr() + with tempfile.NamedTemporaryFile( + delete=True, suffix='.png', dir='/tmp') as temp_file: + temp_filename = temp_file.name + QR.save(temp_filename) + printer.image(f"{temp_filename}") + printer.set(align='left', bold=False, height=1, width=1) + text = str("CUFE: " + d['cufe']) + printer.textln(text) + printer.set(align='center', bold=False, height=1, width=1) + printer.text("Sigue nuestras redes sociales\n") + printer.text("@bicipizza\n") + printer.text("Recuerde que la propina es voluntaria.\n") + printer.text("Gracias por visitarnos, vuelva pronto.\n") + printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.ORG.\n") + format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + printer.text(str(format_date_time)+'\n') + if waiter: + printer.text("Atendido Por: \n") + printer.text(str(waiter)+'\n') + + +def print_customer_order_format(printer, d, waiter): + printer.set(align='center', bold=False, height=1, width=1) + format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + printer.text(str(format_date_time)+'\n') + + if waiter: + printer.text("Pedido Por: \n") + printer.text(str(waiter)+'\n') + printer.set( + align='center', bold=False, height=2, width=2, custom_size=True + ) + printer.text('========================\n') + text = 'MESA: ' + str(d['table'] + "\n") + printer.text(text) + printer.text('========================\n') + printer.set(align='left', bold=False, height=6, width=6) + combination_pizza = False + pizza = 0 + for line in d["lines"]: + if line['type'] != 'title': + if combination_pizza and pizza < 2: + printer.set( + align='center', + bold=False, + height=2, + width=2, + custom_size=True) + pizza += 1 + elif pizza >= 2: + combination_pizza = False + printer.set( + align='left', + bold=False, + height=2, + width=2, custom_size=True + ) + else: + printer.set( + align='left', + bold=False, + height=2, + width=2, + custom_size=True) + + text = line['product'] + " " + str(line['quantity']) + "\n" + printer.text(text) + if line['description']: + text = line['description'] + "\n" + printer.text(text) + + if pizza == 2: + printer.ln() + pizza = 0 + combination_pizza = False + else: + printer.set( + align='left', bold=True, height=2, width=2, custom_size=True + ) + printer.text("\nPIZZA COMBINADA\n") + combination_pizza = True + pizza = 0 + + # if d["deleted_lines"]: + # for line in d["deleted_lines"]: + # text = line['product'] + " " + str( + # line['quantity']) + " " + str( + # line['unit']) + # printer.text(text) + + +def print_bill(data, address, waiter): + d = data + + if address: + printer = PrinterFactory(type_="network", host=address)._get_printer() + printer.open() + print_bill_format(printer, d, waiter) + printer.cut() + printer.close() + else: + printer = PrinterFactory(type_="dummy")._get_printer() + print_bill_format(printer, d, waiter) + ticket_contenido = printer.output + sys.stdout.write(ticket_contenido.decode('utf-8', errors='ignore')) + + +def print_customer_order(data, address, waiter): + d = data + + if address: + printer = PrinterFactory(type_="network", host=address)._get_printer() + printer.open() + print_customer_order_format(printer, d, waiter) + printer.cut() + printer.close() + else: + printer = PrinterFactory(type_="dummy")._get_printer() + print_customer_order_format(printer, d, waiter) + ticket_contenido = printer.output + sys.stdout.write(ticket_contenido.decode('utf-8', errors='ignore')) diff --git a/Api/main.py b/Api/main.py index ae79f8f..e2b6e28 100644 --- a/Api/main.py +++ b/Api/main.py @@ -1,13 +1,7 @@ -from fastapi import FastAPI, Response -from escpos.printer import Dummy -from escpos.printer import Network -import sys import json - +from fastapi import FastAPI, Response from pydantic import BaseModel -from datetime import datetime -import tempfile -from .qr_generator import QRCodeGenerator +from .formats import print_bill, print_customer_order app = FastAPI( title="Print Server FastAPI", @@ -22,183 +16,6 @@ class Info(BaseModel): user_name: str -def print_bill(data, address, waiter): - d = data - # Crea una instancia de la impresora ficticia - # printer = Network(str(address)) - # printer.open() - printer = Dummy() - # Imprime el encabezado - printer.set(align='center', bold=False, height=1, width=1) - printer.text(d["shop_name"]+'\n') - printer.text(d["shop_nit"]+'\n') - printer.text(d["shop_address"]+'\n') - printer.set(align='left', bold=False, height=1, width=1) - printer.textln('===============================================') - text = d['state'] - printer.textln(text) - if d['invoice'] and d['invoice']['resolution']: - text = "Resolucion de Facturacion # " + \ - str(d['invoice']['resolution']['resolution_number']) \ - + "\nValida desde " + \ - d['invoice']['resolution']['valid_date_time_from'] + \ - " hasta "+str(d['invoice']['resolution']['valid_date_time_to']) - printer.textln(text) - printer.ln() - text = "Factura #: " + d['invoice']['invoice_number'] - printer.textln(text) - printer.text("Cliente: " + d["party"]+'\n') - printer.text("CC/NIT: " + d["tax_identifier_code"]+'\n') - printer.text("Direccion: " + d["address"]+'\n') - text = 'MESA: ' + str(d['table'] + "\n") - printer.text(text) - printer.textln('===============================================') - printer.ln() - for line in d["lines"]: - if line['type'] != 'title': - text = line['product'] - printer.text(text) - printer.ln() - text = str(line['quantity']) + " " + " $" + \ - line["unit_price"] + "\n" - printer.text(text) - - printer.set(align='right', bold=False, height=1, width=1) - printer.textln('================================================') - text = "Descuento Realizado: "+str(d["total_discount"])+"\n" - printer.text(text) - text = "Propina: "+str(d["total_tip"])+"\n" - printer.text(text) - text = "Total (sin impuestos): "+str(d["untaxed_amount"])+"\n" - printer.text(text) - text = "Impuestos (INC): "+str(d["tax_amount"])+"\n" - printer.text(text) - text = "Total: "+str(d["total"])+"\n" - printer.text(text) - printer.ln() - if 'payments' in d.keys(): - printer.textln("Pagos: ") - printer.textln('================================================') - - for payment in d['payments']: - text = str(payment["statement"])+" $"+str(payment["amount"]) - printer.textln(text) - printer.set(align='center', bold=False, height=1, width=1) - printer.textln('==============================================\n') - if d["fe_cufe"]: - QR = QRCodeGenerator(d["fe_cufe"]).generate_qr() - with tempfile.NamedTemporaryFile( - delete=True, suffix='.png', dir='/tmp') as temp_file: - temp_filename = temp_file.name - QR.save(temp_filename) - printer.image(f"{temp_filename}") - printer.set(align='left', bold=False, height=1, width=1) - text = str("CUFE: " + d['cufe']) - printer.textln(text) - printer.set(align='center', bold=False, height=1, width=1) - printer.text("Sigue nuestras redes sociales\n") - printer.text("@bicipizza\n") - printer.text("Recuerde que la propina es voluntaria.\n") - printer.text("Gracias por visitarnos, vuelva pronto.\n") - printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.ORG.\n") - format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - printer.text(str(format_date_time)+'\n') - if waiter: - printer.text("Atendido Por: \n") - printer.text(str(waiter)+'\n') - - # Corta el papel (solo para impresoras que soportan esta función) - # printer.cut() - # printer.close() - # Obtiene el contenido del ticket de prueba - ticket_contenido = printer.output - # Imprime el contenido en la consola - sys.stdout.write(ticket_contenido.decode('utf-8', errors='ignore')) - - -def print_customer_order(data, address, waiter): - d = data - # Crea una instancia de la impresora ficticia - printer = Network(str(address)) - printer.open() - # printer = Dummy() - # Imprime el encabezado - printer.set(align='center', bold=False, height=1, width=1) - format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - printer.text(str(format_date_time)+'\n') - - if waiter: - printer.text("Pedido Por: \n") - printer.text(str(waiter)+'\n') - printer.set( - align='center', bold=False, height=2, width=2, custom_size=True - ) - printer.text('========================\n') - text = 'MESA: ' + str(d['table'] + "\n") - printer.text(text) - printer.text('========================\n') - printer.set(align='left', bold=False, height=6, width=6) - combination_pizza = False - pizza = 0 - for line in d["lines"]: - if line['type'] != 'title': - if combination_pizza and pizza < 2: - printer.set( - align='center', - bold=False, - height=2, - width=2, - custom_size=True) - pizza += 1 - elif pizza >= 2: - combination_pizza = False - printer.set( - align='left', - bold=False, - height=2, - width=2, custom_size=True - ) - else: - printer.set( - align='left', - bold=False, - height=2, - width=2, - custom_size=True) - - text = line['product'] + " " + str(line['quantity']) + "\n" - printer.text(text) - if line['description']: - text = line['description'] + "\n" - printer.text(text) - - if pizza == 2: - printer.ln() - pizza = 0 - combination_pizza = False - else: - printer.set( - align='left', bold=True, height=2, width=2, custom_size=True - ) - printer.text("\nPIZZA COMBINADA\n") - combination_pizza = True - pizza = 0 - # if d["deleted_lines"]: - # for line in d["deleted_lines"]: - # text = line['product'] + " " + str( - # line['quantity']) + " " + str( - # line['unit']) - # printer.text(text) - # Corta el papel (solo para impresoras que soportan esta función) - printer.cut() - printer.close() - # Obtiene el contenido del ticket de prueba - # ticket_contenido = printer.output - - # Imprime el contenido en la consola - # sys.stdout.write(ticket_contenido.decode('utf-8', errors='replace')) - - @app.post("/print_bill") def print_ticket_bill(info: Info): info = dict(info) diff --git a/Api/printer_factory.py b/Api/printer_factory.py new file mode 100644 index 0000000..5c54c6e --- /dev/null +++ b/Api/printer_factory.py @@ -0,0 +1,17 @@ +from escpos.printer import Dummy, Network + + +class PrinterFactory: + + def __init__(self, type_="dummy", host="0.0.0.0", port=9100): + self.type_ = type_ + self.host = host + self.port = port + + def _get_printer(self): + if self.type_ == "dummy": + return Dummy() + elif self.type_ == "network": + return Network(self.host, self.port) + else: + raise ValueError(f"Tipo de impresora no soportado: {self.type_}") diff --git a/Api/test/test_main.py b/Api/test/test_main.py index 3611ef6..6eb7cc5 100644 --- a/Api/test/test_main.py +++ b/Api/test/test_main.py @@ -18,7 +18,7 @@ def test_print_bill(): json.dumps( load_json('test/fixtures/bill.json') )), - "ip_printer": "192.168.1.105", + "ip_printer": "", "user_name": "Juan" } @@ -33,7 +33,7 @@ def test_print_customer_order(): json.dumps( load_json('test/fixtures/customer_order.json') )), - "ip_printer": "192.168.1.100", + "ip_printer": "", "user_name": "Juan" } @@ -50,7 +50,7 @@ def test_print_customer_order_deleted_lines(): load_json( 'test/fixtures/customer_order_deleted_lines.json') )), - "ip_printer": "192.168.1.110", + "ip_printer": "", "user_name": "Juan" } @@ -66,7 +66,7 @@ def test_print_bar_order(): json.dumps( load_json('test/fixtures/customer_order.json') )), - "ip_printer": "192.168.1.110", + "ip_printer": "", "user_name": "Juan" } diff --git a/Api/test/test_printerfactory.py b/Api/test/test_printerfactory.py new file mode 100644 index 0000000..6811801 --- /dev/null +++ b/Api/test/test_printerfactory.py @@ -0,0 +1,41 @@ +import unittest +from unittest.mock import patch, MagicMock +from escpos.printer import Dummy +from ..printer_factory import PrinterFactory + + +class TestPrinterFactory(unittest.TestCase): + + def setUp(self): + self.factory_dummy = PrinterFactory(type_="dummy") + self.factory_network = PrinterFactory( + type_="network", host="192.168.1.100") + + def test_create_dummy_printer(self): + """Test creación de impresora dummy""" + + printer = self.factory_dummy._get_printer() + self.assertIsInstance(printer, Dummy) + + @patch('Api.printer_factory.Network') + def test_create_network_printer(self, mock_network): + """Test creación de impresora de red con mock""" + + mock_printer_instance = MagicMock() + mock_network.return_value = mock_printer_instance + + printer = self.factory_network._get_printer() + + mock_network.assert_called_once_with("192.168.1.100", 9100) + + self.assertEqual(printer, mock_printer_instance) + + def test_create_unknown_printer_type(self): + """Test para tipo de impresora desconocido""" + + with self.assertRaises(ValueError): + PrinterFactory(type_="invalid_type")._get_printer() + + +if __name__ == '__main__': + unittest.main() diff --git a/docker-compose.yml b/docker-compose.yml index 80f351e..8054087 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,4 @@ -version: '3.8' - services: - # Servicio de FastAPI escpos: build: . container_name: escpos