Files
oc-api_print/Api/formats.py
2025-10-21 14:53:02 -05:00

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