fix: Refactor Tickets Formats

This commit is contained in:
2025-10-21 14:47:37 -05:00
parent 835de8359e
commit 9d874486ae

View File

@@ -1,196 +1,267 @@
#!/usr/bin/env python3
import sys
import tempfile
import pytz
from datetime import datetime
from typing import Dict, Any, Optional
import pytz
from qr_generator import QRCodeGenerator
from printer_factory import PrinterFactory
def get_current_time_america_bogota():
format_ = "%Y-%m-%d %H:%M:%S"
america_bogota_tz = pytz.timezone('America/Bogota')
format_date_time = datetime.now(america_bogota_tz)
class TicketPrinter:
"""Clase principal para manejar la impresión de tickets"""
return format_date_time.strftime(format_)
def __init__(self, address: Optional[str] = None):
self.address = address
self.printer = self._get_printer()
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")
printer.text(str(get_current_time_america_bogota())+'\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)
printer.text(str(get_current_time_america_bogota())+'\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
def _get_printer(self):
"""Obtiene la instancia de impresora según la configuración"""
if self.address:
printer = PrinterFactory(
type_="network", host=self.address)._get_printer()
printer.open()
return printer
else:
printer.set(
align='left', bold=True, height=2, width=2, custom_size=True
return PrinterFactory(type_="dummy")._get_printer()
def _close_printer(self):
"""Cierra la conexión con la impresora si es necesaria"""
if self.address and hasattr(self.printer, 'close'):
self.printer.close()
def _get_current_time_bogota(self) -> str:
"""Obtiene la hora actual en formato America/Bogota"""
format_ = "%Y-%m-%d %H:%M:%S"
bogota_tz = pytz.timezone('America/Bogota')
return datetime.now(bogota_tz).strftime(format_)
class BillPrinter(TicketPrinter):
"""Manejador específico para facturas"""
def print_bill(self, data: Dict[str, Any], waiter: Optional[str] = None):
"""Imprime una factura"""
try:
self._print_bill_content(data, waiter)
if self.address:
self.printer.cut()
else:
self._print_dummy_output()
finally:
self._close_printer()
def _print_bill_content(self, data: Dict[str, Any], waiter: Optional[str]):
"""Genera el contenido de la factura"""
self._print_header(data)
self._print_invoice_info(data)
self._print_customer_info(data)
self._print_items(data)
self._print_totals(data)
self._print_payments(data)
self._print_qr_code(data)
self._print_footer(waiter)
def _print_header(self, data: Dict[str, Any]):
"""Imprime el encabezado de la factura"""
self.printer.set(align='center', bold=False, height=1, width=1)
self.printer.text(f"{data['shop_name']}\n")
self.printer.text(f"{data['shop_nit']}\n")
self.printer.text(f"{data['shop_address']}\n")
self._print_separator()
def _print_invoice_info(self, data: Dict[str, Any]):
"""Imprime información de facturación"""
self.printer.set(align='left', bold=False, height=1, width=1)
self.printer.textln(data['state'])
if data.get('invoice') and data['invoice'].get('resolution'):
resolution = data['invoice']['resolution']
resolution_number = resolution['resolution_number']
self.printer.textln(
f"Resolucion de Facturacion # {resolution_number}\n"
f"Valida desde {resolution['valid_date_time_from']} "
f"hasta {resolution['valid_date_time_to']}"
)
printer.text("\nPIZZA COMBINADA\n")
combination_pizza = True
pizza = 0
self.printer.ln()
self.printer.textln(
f"Factura #: {data['invoice']['invoice_number']}")
# if d["deleted_lines"]:
# for line in d["deleted_lines"]:
# text = line['product'] + " " + str(
# line['quantity']) + " " + str(
# line['unit'])
# printer.text(text)
def _print_customer_info(self, data: Dict[str, Any]):
"""Imprime información del cliente"""
self.printer.text(f"Cliente: {data['party']}\n")
self.printer.text(f"CC/NIT: {data['tax_identifier_code']}\n")
self.printer.text(f"Direccion: {data['address']}\n")
self.printer.text(f"MESA: {data['table']}\n")
self._print_separator()
self.printer.ln()
def _print_items(self, data: Dict[str, Any]):
"""Imprime los items de la factura"""
for line in data["lines"]:
if line['type'] != 'title':
self.printer.text(line['product'])
self.printer.ln()
self.printer.text(
f"{line['quantity']} ${line['unit_price']}\n")
def _print_totals(self, data: Dict[str, Any]):
"""Imprime los totales"""
self.printer.set(align='right', bold=False, height=1, width=1)
self._print_separator()
self.printer.text(f"Descuento Realizado: {data['total_discount']}\n")
self.printer.text(f"Propina: {data['total_tip']}\n")
self.printer.text(f"Total (sin impuestos): {data['untaxed_amount']}\n")
self.printer.text(f"Impuestos (INC): {data['tax_amount']}\n")
self.printer.text(f"Total: {data['total']}\n")
self.printer.ln()
def _print_payments(self, data: Dict[str, Any]):
"""Imprime información de pagos"""
if 'payments' in data:
self.printer.textln("Pagos: ")
self._print_separator()
for payment in data['payments']:
self.printer.textln(
f"{payment['statement']} ${payment['amount']}")
def _print_qr_code(self, data: Dict[str, Any]):
"""Imprime el código QR si está disponible"""
if data.get("fe_cufe"):
self.printer.set(align='center', bold=False, height=1, width=1)
self._print_separator()
qr = QRCodeGenerator(data["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)
self.printer.image(temp_filename)
self.printer.set(align='left', bold=False, height=1, width=1)
self.printer.textln(f"CUFE: {data['cufe']}")
def _print_footer(self, waiter: Optional[str]):
"""Imprime el pie de página"""
self.printer.set(align='center', bold=False, height=1, width=1)
self._print_separator()
self.printer.text("Sigue nuestras redes sociales\n")
self.printer.text("@bicipizza\n")
self.printer.text("Recuerde que la propina es voluntaria.\n")
self.printer.text("Gracias por visitarnos, vuelva pronto.\n")
self.printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.ORG.\n")
self.printer.text(f"{self._get_current_time_bogota()}\n")
if waiter:
self.printer.text("Atendido Por: \n")
self.printer.text(f"{waiter}\n")
def _print_separator(self):
"""Imprime una línea separadora"""
self.printer.textln('================================================')
def _print_dummy_output(self):
"""Muestra la salida cuando no hay impresora real"""
ticket_content = self.printer.output
sys.stdout.write(ticket_content.decode('utf-8', errors='ignore'))
class CustomerOrderPrinter(TicketPrinter):
"""Manejador específico para órdenes de cliente"""
def print_order(self, data: Dict[str, Any], waiter: Optional[str] = None):
"""Imprime una orden de cliente"""
try:
self._print_order_content(data, waiter)
if self.address:
self.printer.cut()
else:
self._print_dummy_output()
finally:
self._close_printer()
def _print_order_content(
self, data: Dict[str, Any], waiter: Optional[str]):
"""Genera el contenido de la orden"""
self._print_header(waiter)
self._print_table_info(data)
self._print_order_items(data)
def _print_header(self, waiter: Optional[str]):
"""Imprime el encabezado de la orden"""
self.printer.set(align='center', bold=False, height=1, width=1)
self.printer.text(f"{self._get_current_time_bogota()}\n")
if waiter:
self.printer.text("Pedido Por: \n")
self.printer.text(f"{waiter}\n")
def _print_table_info(self, data: Dict[str, Any]):
"""Imprime información de la mesa"""
self.printer.set(
align='center', bold=False, height=2, width=2, custom_size=True)
self.printer.text('========================\n')
self.printer.text(f"MESA: {data['table']}\n")
self.printer.text('========================\n')
def _print_order_items(self, data: Dict[str, Any]):
"""Imprime los items de la orden"""
combination_pizza = False
pizza_count = 0
for line in data["lines"]:
if line['type'] != 'title':
self._set_item_format(combination_pizza, pizza_count)
text = f"{line['product']} {line['quantity']}\n"
self.printer.text(text)
if line.get('description'):
self.printer.text(f"{line['description']}\n")
if combination_pizza:
pizza_count += 1
if pizza_count >= 2:
self.printer.ln()
pizza_count = 0
combination_pizza = False
else:
self._print_combined_pizza_header()
combination_pizza = True
pizza_count = 0
def _set_item_format(self, is_combined: bool, pizza_count: int):
"""Configura el formato según el tipo de item"""
if is_combined and pizza_count < 2:
self.printer.set(
align='center',
bold=False,
height=2,
width=2,
custom_size=True)
else:
self.printer.set(
align='left', bold=False, height=2, width=2, custom_size=True)
def _print_combined_pizza_header(self):
"""Imprime el encabezado para pizza combinada"""
self.printer.set(
align='left', bold=True, height=2, width=2, custom_size=True)
self.printer.text("\nPIZZA COMBINADA\n")
def _print_dummy_output(self):
"""Muestra la salida cuando no hay impresora real"""
ticket_content = self.printer.output
sys.stdout.write(ticket_content.decode('utf-8', errors='ignore'))
# Funciones de interfaz para mantener compatibilidad
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'))
printer = BillPrinter(address)
printer.print_bill(data, waiter)
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'))
printer = CustomerOrderPrinter(address)
printer.print_order(data, waiter)