#!/usr/bin/env python3 import sys import tempfile from datetime import datetime from typing import Dict, Any, Optional import pytz from qr_generator import QRCodeGenerator from printer_factory import PrinterFactory class TicketPrinter: """Clase principal para manejar la impresión de tickets""" def __init__(self, address: Optional[str] = None): self.address = address self.printer = self._get_printer() 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: 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']}" ) self.printer.ln() self.printer.textln( f"Factura #: {data['invoice']['invoice_number']}") 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'))