257 lines
9.8 KiB
Python
257 lines
9.8 KiB
Python
#!/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'))
|