17 Commits

Author SHA1 Message Date
d7e682c1ec Configuration api print Calzabana 2025-09-21 14:32:58 -05:00
bff20cbd72 Feat(WIP): Lineas Eliminadas 2024-10-20 12:46:10 -05:00
d1482056c0 Feat: Impresion de CUFE Factura electronica colombia 2024-10-19 14:03:27 -05:00
458ff6f89f Fix: Test impresora fisica 2024-10-19 13:54:24 -05:00
1b8619f95e Tests: Flake8, Rakefile 2024-10-19 10:56:58 -05:00
9ee21faba6 Feat: Test customer_order, bar_order 2024-10-19 10:23:39 -05:00
f697200d13 Fix: import test 2024-10-19 09:57:28 -05:00
4d5a88145d Fix: Modificacion estructura del Proyecto 2024-10-19 09:35:55 -05:00
d69441779b Fix: app name 2024-10-19 00:45:09 -05:00
83ea8b2ac5 Feat: Implementacion Impresion de QR 2024-10-19 00:44:50 -05:00
022dbc35c5 Fix: Path fixture 2024-10-18 23:09:06 -05:00
4919087148 chore: Ignore error decode utf-8 2024-10-18 22:53:31 -05:00
e1828a8c80 Feat(WIP): Se agrega soporte para generacion de QR's 2024-10-18 22:50:21 -05:00
674209a994 Feat: Despliegue Docker 2024-10-18 22:46:50 -05:00
2157e8a015 feat: Se añade Propina al final de la cuenta 2024-02-27 17:09:12 +00:00
Rodia
be238e8e48 revert 81acf82312
revert feat: Se añade tipo USB
2023-12-22 07:39:09 -05:00
rodia
81acf82312 feat: Se añade tipo USB 2023-12-22 07:47:23 -05:00
20 changed files with 846 additions and 64 deletions

0
Api/__init__.py Normal file
View File

BIN
Api/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

242
Api/main.py Normal file
View File

@@ -0,0 +1,242 @@
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 PIL import Image
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()
# 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')
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)

238
Api/main2.py Normal file
View File

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

23
Api/qr_generator.py Normal file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
import qrcode
class QRCodeGenerator:
"""Qr Generato"""
def __init__(self, url):
self.url = url
def generate_qr(self):
"""Genera un código QR a partir de la URL"""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=6,
border=4,
)
qr.add_data(self.url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
return img

0
Api/test/__init__.py Normal file
View File

0
Api/test/fixtures/__init__.py vendored Normal file
View File

35
Api/test/fixtures/bill.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{"shop_name": "SE",
"shop_nit": "902929200",
"shop_address": "Calle Arriba 1234",
"fe_cufe": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=936ef10e23f9fbae2d2c70869050b907b7af55a7d2c80a9f2e906450a8f98b20a88f7e4219fd0d02962f6e639b29ba20",
"cufe": "936ef10e23f9fbae2d2c70869050b907b7af55a7d2c80a9f2e906450a8f98b20a88f7e4219fd0d02962f6e639b29ba20",
"invoice": {
"invoice_number": "FPES2068",
"resolution": {
"resolution_number": "18764072418755",
"resolution_prefix": "FPES",
"valid_date_time_from": "2024-06-06",
"valid_date_time_to": "2026-06-06",
"from_number": 1, "to_number": 30000}
}, "party": "00-Consumidor Final",
"tax_identifier_type": "NIT",
"tax_identifier_code": "222222222",
"address": "Dg 74A #C-2-56", "city": "Medellín",
"zone": "CHIMENEA (CH)",
"table": "CH1",
"lines": [
{"type": "line", "product": "Club negra", "quantity": 1.0, "uom": "u", "unit_price": "9800.00", "taxes": "8.00%"},
{"type": "line", "product": "Club roja", "quantity": 1.0, "uom": "u", "unit_price": "9800.00", "taxes": "8.00%"},
{"type": "line", "product": "Aguila ligth", "quantity": 1.0, "uom": "u", "unit_price": "8600.00", "taxes": "8.00%"},
{"type": "line", "product": "Propinas", "quantity": 1.0, "uom": "u", "unit_price": "27333.00", "taxes": null}],
"total_discount": "0.00",
"untaxed_amount": "300666.30",
"tax_amount": "21866.67",
"total_tip": "10000",
"total": "322532.97",
"state": "CUENTA FINAL",
"payments": [
{"statement": "Transferencia TPV", "amount": "222532.00"},
{"statement": "Efectivo TPV", "amount": "100000.97"}
]
}

24
Api/test/fixtures/customer_order.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"party": "00-Consumidor Final",
"tax_identifier_type": "NIT",
"tax_identifier_code": "222222222",
"address": "Dg 74A #C-2-56",
"city": "Medellín",
"zone": "CHIMENEA (CH)",
"table": "CH1",
"lines": [
{"type": "line", "product": "Club negra", "description": "", "quantity": 1.0, "uom": "Unidad"},
{"type": "line", "product": "Club roja", "description": "", "quantity": 1.0, "uom": "Unidad"},
{"type": "line", "product": "Aguila ligth", "description": "", "quantity": 1.0, "uom": "Unidad"},
{"type": "line", "product": "Té hatsu", "description": "", "quantity": 1.0, "uom": "Unidad"},
{"type": "line", "product": "Aromatica frutos rojos", "description": "", "quantity": 1.0, "uom": "Unidad"},
{"type": "title", "product": null, "description": null, "quantity": null, "uom": null},
{"type": "line", "product": "Santiago Botero", "description": "", "quantity": 0.5, "uom": "Media Unidad"},
{"type": "line", "product": "Villa Clara", "description": "", "quantity": 0.5, "uom": "Media Unidad"},
{"type": "title", "product": null, "description": null, "quantity": null, "uom": null},
{"type": "line", "product": "Granma", "description": "", "quantity": 1.0, "uom": "Media Unidad"},
{"type": "title", "product": null, "description": null, "quantity": null, "uom": null},
{"type": "line", "product": "Camilo Cienfuegos", "description": "", "quantity": 0.5, "uom": "Media Unidad"},
{"type": "line", "product": "Propinas", "description": null, "quantity": 1.0, "uom": "Unidad"}],
"deleted_lines": []
}

View File

@@ -0,0 +1,13 @@
{
"party": "00-Consumidor Final",
"tax_identifier_type": "NIT",
"tax_identifier_code": "222222222",
"address": "Dg 74A #C-2-56",
"city": "Medellín",
"zone": "SALON",
"table": "SL5",
"lines": [],
"deleted_lines": [
{"product": "Playa Girón", "quantity": -1.0, "unit": "Media Unidad"},
{"product": "Ciclobi", "quantity": -1.0, "unit": "Media Unidad"}
]}

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
from ..qr_generator import QRCodeGenerator
def test_generate_qr():
url = "https://www.gnu.org/"
qr_generator = QRCodeGenerator(url)
filename = qr_generator.generate_qr()
print(filename)

76
Api/test/test_main.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
from fastapi.testclient import TestClient
from ..main import app
import json
client = TestClient(app)
def load_json(file_path):
with open(file_path, 'r') as filejson:
return json.load(filejson)
def test_print_bill():
test_info = {
"content": str(
json.dumps(
load_json('test/fixtures/bill.json')
)),
"ip_printer": "192.168.1.105",
"user_name": "Juan"
}
response = client.post("/print_bill", json=test_info)
assert response.status_code == 200
assert response.content.decode() == "!Impresion Realizada!"
def test_print_customer_order():
test_info = {
"content": str(
json.dumps(
load_json('test/fixtures/customer_order.json')
)),
"ip_printer": "192.168.1.100",
"user_name": "Juan"
}
response = client.post("/order_kitchen", json=test_info)
assert response.status_code == 200
assert response.content.decode() == "!Impresion Realizada!"
def test_print_customer_order_deleted_lines():
test_info = {
"content": str(
json.dumps(
load_json(
'test/fixtures/customer_order_deleted_lines.json')
)),
"ip_printer": "192.168.1.110",
"user_name": "Juan"
}
response = client.post("/order_kitchen", json=test_info)
assert response.status_code == 200
assert response.content.decode() == "!Impresion Realizada!"
def test_print_bar_order():
test_info = {
"content": str(
json.dumps(
load_json('test/fixtures/customer_order.json')
)),
"ip_printer": "192.168.1.110",
"user_name": "Juan"
}
response = client.post("/order_bar", json=test_info)
assert response.status_code == 200
assert response.content.decode() == "!Impresion Realizada!"

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM python:3.11-slim
WORKDIR /app
# 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/*
# 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 . .
EXPOSE 8000
CMD ["uvicorn", "Api.main:app", "--host", "0.0.0.0", "--port", "8000"]

62
Rakefile Normal file
View File

@@ -0,0 +1,62 @@
require 'bundler/setup'
$:.unshift File.expand_path('../lib', __FILE__)
DOCKER_COMPOSE='docker-compose.yml'
desc 'entorno vivo'
namespace :live do
task :up do
compose('up', '--build', '-d', compose: DOCKER_COMPOSE)
end
desc 'monitorear salida'
task :tail do
compose('logs', '-f', 'escpos', compose: DOCKER_COMPOSE)
end
desc 'monitorear salida'
task :tail_end do
compose('logs', '-f', '-n 50', 'escpos', compose: DOCKER_COMPOSE)
end
desc 'detener entorno'
task :down do
compose('down', compose: DOCKER_COMPOSE)
end
desc 'detener entorno'
task :stop do
compose('stop', compose: DOCKER_COMPOSE)
end
desc 'eliminar entorno'
task :del do
compose('down', '-v', '--rmi', 'all', compose: DOCKER_COMPOSE)
end
desc 'reiniciar entorno'
task :restart do
compose('restart', compose: DOCKER_COMPOSE)
end
desc 'detener entorno'
task :stop do
compose('stop', compose: DOCKER_COMPOSE)
end
desc 'terminal'
task :sh do
compose('exec', 'escpos', 'bash')
end
end
desc 'iterar'
task :tdd do
compose('exec', 'escpos', "bash -c 'cd Api && flake8 *'")
compose('exec', 'escpos', "bash -c 'cd Api && pytest -vvv'")
end
def compose(*arg, compose: DOCKER_COMPOSE)
sh "docker compose -f #{compose} #{arg.join(' ')}"
end

0
assets/logo.png Normal file
View File

17
docker-compose.yml Normal file
View File

@@ -0,0 +1,17 @@
version: '3.8'
services:
escpos:
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

BIN
logo.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,10 +1,13 @@
from fastapi import FastAPI, Response from fastapi import FastAPI, Response
from escpos.printer import Dummy, Network, Usb from escpos.printer import Dummy
# import sys from escpos.printer import Network
import sys
import json import json
from pydantic import BaseModel from pydantic import BaseModel
from datetime import datetime from datetime import datetime
import tempfile
from .qr_generator import QRCodeGenerator
app = FastAPI( app = FastAPI(
title="Print Server FastAPI", title="Print Server FastAPI",
@@ -17,36 +20,19 @@ class Info(BaseModel):
content: str content: str
ip_printer: str ip_printer: str
user_name: str user_name: str
printer_type: str
idVendor: str
idProduct: str
def open_conexion(info): def print_bill(data, address, waiter):
if info.get('ip_printer'):
# Crea una instancia de la impresora de Red
printer = Network(str(info.get('ip_printer')))
printer.open()
elif info.get('idVendor') and info.get('idProduct'):
# Crea una instancia de la impresora USB
printer = Usb(
int(info.get('idVendor')),
int(info.get('idProduct')), 0)
else:
# Crea una instancia de la impresora ficticia
printer = Dummy()
return printer
def print_bill(data, info, waiter=None):
d = data d = data
# Crea una instancia de la impresora ficticia
# Abre Conexión Con la Impresora printer = Network(str(address))
printer = open_conexion(info) # printer.open()
# printer = Dummy()
# Imprime el encabezado
printer.set(align='center', bold=False, height=1, width=1) printer.set(align='center', bold=False, height=1, width=1)
printer.text(d["shop_name"] + '\n') printer.text(d["shop_name"]+'\n')
printer.text(d["shop_address"] + '\n') printer.text(d["shop_nit"]+'\n')
printer.text(d["shop_address"]+'\n')
printer.set(align='left', bold=False, height=1, width=1) printer.set(align='left', bold=False, height=1, width=1)
printer.textln('===============================================') printer.textln('===============================================')
text = d['state'] text = d['state']
@@ -56,15 +42,14 @@ def print_bill(data, info, waiter=None):
str(d['invoice']['resolution']['resolution_number']) \ str(d['invoice']['resolution']['resolution_number']) \
+ "\nValida desde " + \ + "\nValida desde " + \
d['invoice']['resolution']['valid_date_time_from'] + \ d['invoice']['resolution']['valid_date_time_from'] + \
" hasta " + str(d['invoice']['resolution']['valid_date_time_to']) " hasta "+str(d['invoice']['resolution']['valid_date_time_to'])
printer.textln(text) printer.textln(text)
printer.ln() printer.ln()
text = "Factura #: " + d['invoice']['invoice_number'] text = "Factura #: " + d['invoice']['invoice_number']
printer.textln(text) printer.textln(text)
printer.text("Cliente: " + d["party"] + '\n') printer.text("Cliente: " + d["party"]+'\n')
printer.text("CC/NIT: " + d["tax_identifier_code"] + '\n') printer.text("CC/NIT: " + d["tax_identifier_code"]+'\n')
printer.text("Direccion: " + d["address"] + '\n') printer.text("Direccion: " + d["address"]+'\n')
text = 'MESA: ' + str(d['table'] + "\n")
printer.text(text) printer.text(text)
printer.textln('===============================================') printer.textln('===============================================')
printer.ln() printer.ln()
@@ -79,13 +64,13 @@ def print_bill(data, info, waiter=None):
printer.set(align='right', bold=False, height=1, width=1) printer.set(align='right', bold=False, height=1, width=1)
printer.textln('================================================') printer.textln('================================================')
text = "Descuento Realizado: " + str(d["total_discount"]) + "\n" text = "Descuento Realizado: "+str(d["total_discount"])+"\n"
printer.text(text) printer.text(text)
text = "Total (sin impuestos): " + str(d["untaxed_amount"]) + "\n" text = "Total (sin impuestos): "+str(d["untaxed_amount"])+"\n"
printer.text(text) printer.text(text)
text = "Impuestos (INC): " + str(d["tax_amount"]) + "\n" text = "Impuestos (INC): "+str(d["tax_amount"])+"\n"
printer.text(text) printer.text(text)
text = "Total: " + str(d["total"]) + "\n" text = "Total: "+str(d["total"])+"\n"
printer.text(text) printer.text(text)
printer.ln() printer.ln()
if 'payments' in d.keys(): if 'payments' in d.keys():
@@ -93,42 +78,52 @@ def print_bill(data, info, waiter=None):
printer.textln('================================================') printer.textln('================================================')
for payment in d['payments']: for payment in d['payments']:
text = str(payment["statement"]) + " $" + str(payment["amount"]) text = str(payment["statement"])+" $"+str(payment["amount"])
printer.textln(text) printer.textln(text)
printer.set(align='center', bold=False, height=1, width=1) printer.set(align='center', bold=False, height=1, width=1)
printer.textln('==============================================\n') printer.textln('==============================================\n')
printer.text("Sigue nuestras redes sociales\n") #if d["fe_cufe"]:
printer.text("@Rustik\n") # QR = QRCodeGenerator(d["fe_cufe"]).generate_qr()
printer.text("Recuerde que la propina es voluntaria.\n") # 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("Gracias por visitarnos, vuelva pronto.\n")
printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.ORG.\n") printer.text("SOFTWARE POTENCIADO POR ONECLUSTER.ORG.\n")
format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
printer.text(str(format_date_time) + '\n') printer.text(str(format_date_time)+'\n')
if waiter: if waiter:
printer.text("Atendido Por: \n") printer.text("Atendido Por: \n")
printer.text(str(waiter) + '\n') printer.text(str(waiter)+'\n')
# Corta el papel (solo para impresoras que soportan esta función) # Corta el papel (solo para impresoras que soportan esta función)
printer.cut() printer.cut()
printer.close() printer.close()
# Obtiene el contenido del ticket de prueba # Obtiene el contenido del ticket de prueba
# ticket_contenido = printer.output ticket_contenido = printer.output
# Imprime el contenido en la consola # Imprime el contenido en la consola
# sys.stdout.write(ticket_contenido.decode('utf-8')) sys.stdout.write(ticket_contenido.decode('utf-8', errors='ignore'))
def print_customer_order(data, info, waiter): def print_customer_order(data, address, waiter):
d = data d = data
# Abre Conexión Con la Impresora # Crea una instancia de la impresora ficticia
printer = open_conexion(info) printer = Network(str(address))
printer.open()
# printer = Dummy()
# Imprime el encabezado # Imprime el encabezado
printer.set(align='center', bold=False, height=1, width=1) printer.set(align='center', bold=False, height=1, width=1)
format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') format_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
printer.text(str(format_date_time) + '\n') printer.text(str(format_date_time)+'\n')
if waiter: if waiter:
printer.text("Pedido Por: \n") printer.text("Pedido Por: \n")
printer.text(str(waiter) + '\n') printer.text(str(waiter)+'\n')
printer.set( printer.set(
align='center', bold=False, height=2, width=2, custom_size=True align='center', bold=False, height=2, width=2, custom_size=True
) )
@@ -142,17 +137,27 @@ def print_customer_order(data, info, waiter):
for line in d["lines"]: for line in d["lines"]:
if line['type'] != 'title': if line['type'] != 'title':
if combination_pizza and pizza < 2: if combination_pizza and pizza < 2:
printer.set(align='center', bold=False, height=2, width=2, printer.set(
custom_size=True) align='center',
bold=False,
height=2,
width=2,
custom_size=True)
pizza += 1 pizza += 1
elif pizza >= 2: elif pizza >= 2:
combination_pizza = False combination_pizza = False
printer.set( printer.set(
align='left', bold=False, height=2, width=2, align='left',
custom_size=True) bold=False,
height=2,
width=2, custom_size=True
)
else: else:
printer.set( printer.set(
align='left', bold=False, height=2, width=2, align='left',
bold=False,
height=2,
width=2,
custom_size=True) custom_size=True)
text = line['product'] + " " + str(line['quantity']) + "\n" text = line['product'] + " " + str(line['quantity']) + "\n"
@@ -172,6 +177,12 @@ def print_customer_order(data, info, waiter):
printer.text("\nPIZZA COMBINADA\n") printer.text("\nPIZZA COMBINADA\n")
combination_pizza = True combination_pizza = True
pizza = 0 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) # Corta el papel (solo para impresoras que soportan esta función)
printer.cut() printer.cut()
printer.close() printer.close()
@@ -179,18 +190,19 @@ def print_customer_order(data, info, waiter):
# ticket_contenido = printer.output # ticket_contenido = printer.output
# Imprime el contenido en la consola # Imprime el contenido en la consola
# sys.stdout.write(ticket_contenido.decode('utf-8')) # sys.stdout.write(ticket_contenido.decode('utf-8', errors='replace'))
@app.post("/print_bill") @app.post("/print_bill")
def print_ticket_bill(info: Info): def print_ticket_bill(info: Info):
info = dict(info) info = dict(info)
data = info["content"] data = info["content"]
address = info["ip_printer"]
waiter = info["user_name"] waiter = info["user_name"]
data = json.loads(data.replace("'", "\"")) data = json.loads(data.replace("'", "\""))
print_bill(data, info, waiter) print_bill(data, address, waiter)
message = "!Impresión Realizada!" message = "!Impresion Realizada!"
return Response(content=message, status_code=200) return Response(content=message, status_code=200)
@@ -198,26 +210,26 @@ def print_ticket_bill(info: Info):
@app.post("/order_kitchen") @app.post("/order_kitchen")
def print_ticket_file_kitchen(info: Info): def print_ticket_file_kitchen(info: Info):
info = dict(info) info = dict(info)
print(info)
data = info["content"] data = info["content"]
address = info["ip_printer"]
waiter = info["user_name"] waiter = info["user_name"]
data = json.loads(data.replace("'", "\"")) data = json.loads(data.replace("'", "\""))
print_customer_order(data, info, waiter) print_customer_order(data, address, waiter)
message = "!Impresión Realizada!" message = "!Impresion Realizada!"
return Response(content=message, status_code=200) return Response(content=message, status_code=200)
@app.post("/order_bar") @app.post("/order_bar")
def print_ticket_file_bar(info: Info): def print_ticket_file_bar(info: Info):
print(info)
info = dict(info) info = dict(info)
data = info["content"] data = info["content"]
address = info["ip_printer"]
waiter = info["user_name"] waiter = info["user_name"]
data = json.loads(data.replace("'", "\"")) data = json.loads(data.replace("'", "\""))
print_customer_order(data, info, waiter) print_customer_order(data, address, waiter)
message = "!Impresión Realizada!" message = "!Impresion Realizada!"
return Response(content=message, status_code=200) return Response(content=message, status_code=200)

7
requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
fastapi
uvicorn[standard]
httpx
pytest
escpos
qrcode
flake8