feat: Se implementa patron Factory

This commit is contained in:
2025-10-21 11:42:57 -05:00
parent bff20cbd72
commit 5cf1958333
6 changed files with 252 additions and 192 deletions

188
Api/formats.py Normal file
View File

@@ -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'))

View File

@@ -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)

17
Api/printer_factory.py Normal file
View File

@@ -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_}")

View File

@@ -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"
}

View File

@@ -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()

View File

@@ -1,7 +1,4 @@
version: '3.8'
services:
# Servicio de FastAPI
escpos:
build: .
container_name: escpos