from datetime import datetime from typing import List import unicodedata import uvicorn from fastapi import FastAPI, HTTPException, Header from pydantic import BaseModel from escpos.printer import Usb from escpos.capabilities import get_profile from PIL import Image MAX_LINE_LEN = 32 SECRET_PASSWORD = "Alkohol1" class OrderItem(BaseModel): menu_item_name: str amount: int class Order(BaseModel): order_id: str order_date: datetime customer_name: str seat_id: str items: List[OrderItem] def get_printer() -> Usb: return Usb(0x28e9, 0x0289, profile="simple") def sanitize_text(text: str) -> str: return unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode() def build_header(order: Order, copy_num: int) -> str: return ( "EZ GG e.V. - EZGG LAN 1.0\n" "--------------------------------\n" f"ID: {order.order_id}\n" f"{order.order_date.strftime('%d.%m. %H:%M')}\n" f"Gast: {sanitize_text(order.customer_name)}\n" f"Sitz: {sanitize_text(order.seat_id)}\n" f"Kopie: {copy_num}\n" "--------------------------------\n" ) def format_order(items: List[OrderItem]) -> List[str]: lines = [] for item in items: name = sanitize_text(item.menu_item_name) line = f"{item.amount}x {name}" if len(line) > MAX_LINE_LEN: line = line[:MAX_LINE_LEN] lines.append(line + "\n") return lines def print_logo(printer_: Usb, path: str = "logo.png"): try: img = Image.open(path) max_width = 384 if img.width > max_width: ratio = max_width / img.width new_height = int(img.height * ratio) img = img.resize((max_width, new_height)) printer_.set(align="center") printer_.image(img) printer_.text("\n") except Exception: printer_.text("[Logo error]\n") def print_order(order: Order, printer_: Usb, copy_num: int) -> None: printer_.set(align="center") print_logo(printer_) printer_.text(build_header(order, copy_num)) printer_.set(align="left") for line in format_order(order.items): printer_.text(line) printer_.text("--------------------------------\n\n") printer_.cut() api = FastAPI() @api.post("/print_order") def print_order_api_endpoint(order: Order, x_password: str = Header(None)): if x_password != SECRET_PASSWORD: raise HTTPException(status_code=401, detail="Unauthorized") for item in order.items: if item.amount < 1: raise HTTPException(status_code=422, detail="Invalid item amount") for copy_num in range(1, 3): try: printer = get_printer() print_order(order, printer, copy_num) printer.close() except Exception as e: raise HTTPException(status_code=500, detail=f"Printing failed: {str(e)}") return {"status": "done"} if __name__ == "__main__": print("Starting receipt printing server...") uvicorn.run(api, host="0.0.0.0", port=5000, log_level="warning")