feat: initial commit — local services (backend + manager dashboard + waiter PWA)

Includes all work to date:
- local_backend: FastAPI backend with products, orders, tables, shifts, cloud sync
- manager_dashboard: React manager UI with product/category management, reports, settings
- waiter_pwa: React PWA for waiter devices
- Category reparent endpoint and UI
- Waiter domain: local_ip sent on heartbeat, waiter_domain persisted from cloud response
- QR code modal in AppInfoTab for waiter domain
- Product form: number input spinners removed, category pre-selected on new product
- Category row: count badge moved to far right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:04:38 +03:00
commit 8ba8c95ecd
209 changed files with 48017 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
"""
Font size comparison test — Jolimark TP850UE
Usage: python print_size_test.py [IP] [PORT]
Default: 10.98.20.25:9100
Prints a single page showing all available size options side by side,
to help decide which sizes to expose in the settings UI.
Hardware facts:
ESC ! (0x1B 0x21 n):
0x10 = double-height only (tall + narrow — breaks aspect ratio)
0x20 = double-width only (short + wide — breaks aspect ratio)
0x30 = double-height + double-width (2x in both axes — correct aspect ratio)
There is NO 1.5x in ESC/POS hardware.
GS ! (0x1D 0x21 n) can go 3x, 4x … 8x but they are extremely large.
"""
import sys
PRINTER_IP = sys.argv[1] if len(sys.argv) > 1 else "10.98.20.25"
PRINTER_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 9100
try:
from escpos.printer import Network
except ImportError:
print("escpos not installed. Run: pip install python-escpos")
sys.exit(1)
def gr(text):
return text.encode('cp737', errors='replace')
def raw(p, b):
p._raw(b)
def section(p, title):
raw(p, b'\x1b\x21\x00')
raw(p, b'\x1b\x45\x00')
raw(p, b'\x1b\x61\x01')
p._raw(gr(f"--- {title} ---\n"))
raw(p, b'\x1b\x61\x00')
def print_sample(p, esc_bang, gs_size, label_en, label_gr):
"""Print one size sample with label."""
# Label at normal size
raw(p, b'\x1b\x21\x00')
raw(p, b'\x1b\x45\x00')
p._raw(gr(f"{label_en}:\n"))
# Apply size via ESC ! and/or GS !
if gs_size is not None:
raw(p, bytes([0x1d, 0x21, gs_size]))
raw(p, bytes([0x1b, 0x21, esc_bang]))
p._raw(gr(f"Club Sandwich. x1\n"))
p._raw(gr(f"* Χωρις αλατι\n"))
p._raw(gr(f"+ Extra Bacon x2\n"))
# Reset
raw(p, b'\x1d\x21\x00')
raw(p, b'\x1b\x21\x00')
raw(p, b'\n')
def divider(p):
raw(p, b'\x1b\x21\x00')
p._raw(gr("-" * 48 + "\n"))
print(f"Connecting to {PRINTER_IP}:{PRINTER_PORT}...")
p = Network(PRINTER_IP, PRINTER_PORT, timeout=10)
raw(p, b'\x1b\x40') # ESC @ reset
raw(p, b'\x1b\x74\x1d') # CP737 Greek
raw(p, b'\x1b\x61\x01')
raw(p, b'\x1b\x21\x30')
raw(p, b'\x1b\x45\x01')
p._raw(gr("SIZE COMPARISON TEST\n"))
raw(p, b'\x1b\x21\x00')
raw(p, b'\x1b\x45\x00')
raw(p, b'\x1b\x61\x00')
p._raw(gr("Which sizes look good for ticket printing?\n\n"))
# ── Section 1: The two aspect-ratio-correct options ───────────────────────
section(p, "CORRECT ASPECT RATIO")
p._raw(gr("\n"))
print_sample(p,
esc_bang=0x00, gs_size=None,
label_en="[1] SMALL (1x1 — normal)",
label_gr="")
print_sample(p,
esc_bang=0x30, gs_size=None,
label_en="[2] LARGE (2x2 — double height+width)",
label_gr="")
# ── Section 2: The broken single-axis options (for comparison) ────────────
divider(p)
section(p, "BROKEN ASPECT RATIO (for comparison)")
p._raw(gr("These scale only ONE axis — shown so\nyou can confirm they look wrong.\n\n"))
print_sample(p,
esc_bang=0x10, gs_size=None,
label_en="[3] Tall only (2x height, 1x width)",
label_gr="")
print_sample(p,
esc_bang=0x20, gs_size=None,
label_en="[4] Wide only (1x height, 2x width)",
label_gr="")
# ── Section 3: GS ! options — 3x and beyond ──────────────────────────────
divider(p)
section(p, "GS! LARGER SIZES (3x3, 4x4)")
p._raw(gr("These are technically available but\nvery large. Shown for completeness.\n\n"))
print_sample(p,
esc_bang=0x00, gs_size=0x22,
label_en="[5] GS! 3x3",
label_gr="")
print_sample(p,
esc_bang=0x00, gs_size=0x33,
label_en="[6] GS! 4x4",
label_gr="")
# ── Conclusion ────────────────────────────────────────────────────────────
divider(p)
raw(p, b'\x1b\x61\x01')
raw(p, b'\x1b\x21\x00')
p._raw(gr("CONCLUSION:\n"))
p._raw(gr("[1] Small = use for modifiers/notes\n"))
p._raw(gr("[2] Large = use for item names/headers\n"))
p._raw(gr("No true 1.5x exists in hardware.\n"))
p._raw(gr("GS! 3x3/4x4 available if desired.\n"))
raw(p, b'\n\n\n')
p.cut()
p.close()
print("Done.")