diff --git a/Api/logo.png b/Api/logo.png new file mode 100644 index 0000000..9c6cb32 Binary files /dev/null and b/Api/logo.png differ diff --git a/Api/main.py b/Api/main.py index ae79f8f..b106abe 100644 --- a/Api/main.py +++ b/Api/main.py @@ -5,8 +5,9 @@ import sys import json from pydantic import BaseModel -from datetime import datetime +from datetime import datetime, timedelta import tempfile +from PIL import Image from .qr_generator import QRCodeGenerator app = FastAPI( @@ -25,11 +26,16 @@ class Info(BaseModel): def print_bill(data, address, waiter): d = data # Crea una instancia de la impresora ficticia - # printer = Network(str(address)) + printer = Network(str(address)) + # Abrir el cajo solo las impreroras que tiene esa funcion tambien puede ser 1 + printer.cashdraw(2) # printer.open() - printer = Dummy() + # Imprimer logo + # printer = Dummy() # Imprime el encabezado printer.set(align='center', bold=False, height=1, width=1) + printer.image("/app/assets/logo.png") + printer.ln() printer.text(d["shop_name"]+'\n') printer.text(d["shop_nit"]+'\n') printer.text(d["shop_address"]+'\n') @@ -37,22 +43,21 @@ def print_bill(data, address, waiter): 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) + #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.textln('====================================') printer.ln() for line in d["lines"]: if line['type'] != 'title': @@ -67,8 +72,6 @@ def print_bill(data, address, waiter): 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" @@ -85,31 +88,29 @@ def print_bill(data, address, waiter): 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) + #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") - format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.COM.CO\n") + adjusted_time = datetime.now() - timedelta(hours=5) + format_date_time = adjusted_time.strftime('%Y-%m-%d %H:%M:%S') printer.text(str(format_date_time)+'\n') if waiter: printer.text("Atendido Por: \n") printer.text(str(waiter)+'\n') # Corta el papel (solo para impresoras que soportan esta función) - # printer.cut() - # printer.close() + printer.cut() + printer.close() # Obtiene el contenido del ticket de prueba ticket_contenido = printer.output # Imprime el contenido en la consola diff --git a/Api/main2.py b/Api/main2.py new file mode 100644 index 0000000..582e77f --- /dev/null +++ b/Api/main2.py @@ -0,0 +1,238 @@ +from fastapi import FastAPI, Response +from escpos.printer import Dummy +from escpos.printer import Network +import sys +import json + +from pydantic import BaseModel +from datetime import datetime, timedelta +import tempfile +from .qr_generator import QRCodeGenerator + +app = FastAPI( + title="Print Server FastAPI", + description="Server that receive request for printing", + version="0.0.1" +) + + +class Info(BaseModel): + content: str + ip_printer: str + user_name: str + + +def print_bill(data, address, waiter): + d = data + # Crea una instancia de la impresora ficticia + printer = Network(str(address)) + # Abrir el cajo solo las impreroras que tiene esa funcion tambien puede ser 1 + printer.cashdraw(2) + # printer.open() + # printer = Dummy() + # Imprime el encabezado + 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') + 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 = "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("Gracias por visitarnos, vuelva pronto.\n") + printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.COM.CO\n") + adjusted_time = datetime.now() - timedelta(hours=5) + format_date_time = adjusted_time.strftime('%Y-%m-%d %H:%M:%S') + printer.text(str(format_date_time)+'\n') + if waiter: + printer.text("Atendido Por: \n") + printer.text(str(waiter)+'\n') + + # Corta el papel (solo para impresoras que soportan esta función) + printer.cut() + printer.close() + # Obtiene el contenido del ticket de prueba + ticket_contenido = printer.output + # Imprime el contenido en la consola + sys.stdout.write(ticket_contenido.decode('utf-8', errors='ignore')) + + +def print_customer_order(data, address, waiter): + d = data + # Crea una instancia de la impresora ficticia + printer = Network(str(address)) + printer.open() + # printer = Dummy() + # Imprime el encabezado + printer.set(align='center', bold=False, height=1, width=1) + format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + printer.text(str(format_date_time)+'\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 + else: + printer.set( + align='left', bold=True, height=2, width=2, custom_size=True + ) + printer.text("\nPIZZA COMBINADA\n") + combination_pizza = True + pizza = 0 + # if d["deleted_lines"]: + # for line in d["deleted_lines"]: + # text = line['product'] + " " + str( + # line['quantity']) + " " + str( + # line['unit']) + # printer.text(text) + # Corta el papel (solo para impresoras que soportan esta función) + printer.cut() + printer.close() + # Obtiene el contenido del ticket de prueba + # ticket_contenido = printer.output + + # Imprime el contenido en la consola + # sys.stdout.write(ticket_contenido.decode('utf-8', errors='replace')) + + +@app.post("/print_bill") +def print_ticket_bill(info: Info): + info = dict(info) + data = info["content"] + address = info["ip_printer"] + waiter = info["user_name"] + data = json.loads(data.replace("'", "\"")) + print_bill(data, address, waiter) + + message = "!Impresion Realizada!" + + return Response(content=message, status_code=200) + + +@app.post("/order_kitchen") +def print_ticket_file_kitchen(info: Info): + info = dict(info) + data = info["content"] + address = info["ip_printer"] + waiter = info["user_name"] + data = json.loads(data.replace("'", "\"")) + print_customer_order(data, address, waiter) + + message = "!Impresion Realizada!" + + return Response(content=message, status_code=200) + + +@app.post("/order_bar") +def print_ticket_file_bar(info: Info): + info = dict(info) + data = info["content"] + address = info["ip_printer"] + waiter = info["user_name"] + data = json.loads(data.replace("'", "\"")) + print_customer_order(data, address, waiter) + + message = "!Impresion Realizada!" + + return Response(content=message, status_code=200) diff --git a/Dockerfile b/Dockerfile index 1981429..a61968f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,23 @@ -# Usa una imagen base de Python FROM python:3.11-slim -# Establece el directorio de trabajo WORKDIR /app -# Copia el archivo de requerimientos y lo instala -COPY ./requirements.txt /tmp/ -RUN pip install --no-cache-dir -r /tmp/requirements.txt +# Instala dependencias para manejo de imágenes +RUN apt-get update && apt-get install -y \ + libjpeg-dev \ + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* -# Copia el código fuente +# Crea directorio para assets +RUN mkdir -p /app/assets + +# Copia requirements e instala dependencias +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copia el resto del código COPY . . -# Expone el puerto que usará FastAPI EXPOSE 8000 -# Comando de arranque CMD ["uvicorn", "Api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml index 80f351e..8810f85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,17 @@ version: '3.8' services: - # Servicio de FastAPI escpos: - build: . + build: + context: . + dockerfile: Dockerfile container_name: escpos command: uvicorn Api.main:app --host 0.0.0.0 --port 8000 --reload + restart: always volumes: - .:/app + - ./logo.png:/app/assets/logo.png # Monta el logo específicamente ports: - "8050:8000" + environment: + - LOGO_PATH=/app/assets/logo.png # Variable de entorno opcional diff --git a/logo.jpeg b/logo.jpeg new file mode 100644 index 0000000..2f2c809 Binary files /dev/null and b/logo.jpeg differ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..259519f Binary files /dev/null and b/logo.png differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..7423010 --- /dev/null +++ b/main.py @@ -0,0 +1,235 @@ +from fastapi import FastAPI, Response +from escpos.printer import Dummy +from escpos.printer import Network +import sys +import json + +from pydantic import BaseModel +from datetime import datetime +import tempfile +from .qr_generator import QRCodeGenerator + +app = FastAPI( + title="Print Server FastAPI", + description="Server that receive request for printing", + version="0.0.1" +) + + +class Info(BaseModel): + content: str + ip_printer: str + user_name: str + + +def print_bill(data, address, waiter): + d = data + # Crea una instancia de la impresora ficticia + printer = Network(str(address)) + # printer.open() + # printer = Dummy() + # Imprime el encabezado + 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') + 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 = "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("Gracias por visitarnos, vuelva pronto.\n") + printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.ORG.\n") + format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + printer.text(str(format_date_time)+'\n') + if waiter: + printer.text("Atendido Por: \n") + printer.text(str(waiter)+'\n') + + # Corta el papel (solo para impresoras que soportan esta función) + printer.cut() + printer.close() + # Obtiene el contenido del ticket de prueba + ticket_contenido = printer.output + # Imprime el contenido en la consola + sys.stdout.write(ticket_contenido.decode('utf-8', errors='ignore')) + + +def print_customer_order(data, address, waiter): + d = data + # Crea una instancia de la impresora ficticia + printer = Network(str(address)) + printer.open() + # printer = Dummy() + # Imprime el encabezado + printer.set(align='center', bold=False, height=1, width=1) + format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + printer.text(str(format_date_time)+'\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 + else: + printer.set( + align='left', bold=True, height=2, width=2, custom_size=True + ) + printer.text("\nPIZZA COMBINADA\n") + combination_pizza = True + pizza = 0 + # if d["deleted_lines"]: + # for line in d["deleted_lines"]: + # text = line['product'] + " " + str( + # line['quantity']) + " " + str( + # line['unit']) + # printer.text(text) + # Corta el papel (solo para impresoras que soportan esta función) + printer.cut() + printer.close() + # Obtiene el contenido del ticket de prueba + # ticket_contenido = printer.output + + # Imprime el contenido en la consola + # sys.stdout.write(ticket_contenido.decode('utf-8', errors='replace')) + + +@app.post("/print_bill") +def print_ticket_bill(info: Info): + info = dict(info) + data = info["content"] + address = info["ip_printer"] + waiter = info["user_name"] + data = json.loads(data.replace("'", "\"")) + print_bill(data, address, waiter) + + message = "!Impresion Realizada!" + + return Response(content=message, status_code=200) + + +@app.post("/order_kitchen") +def print_ticket_file_kitchen(info: Info): + info = dict(info) + data = info["content"] + address = info["ip_printer"] + waiter = info["user_name"] + data = json.loads(data.replace("'", "\"")) + print_customer_order(data, address, waiter) + + message = "!Impresion Realizada!" + + return Response(content=message, status_code=200) + + +@app.post("/order_bar") +def print_ticket_file_bar(info: Info): + info = dict(info) + data = info["content"] + address = info["ip_printer"] + waiter = info["user_name"] + data = json.loads(data.replace("'", "\"")) + print_customer_order(data, address, waiter) + + message = "!Impresion Realizada!" + + return Response(content=message, status_code=200)