fix: Refactor Tickets Formats
This commit is contained in:
425
Api/formats.py
425
Api/formats.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user