_connect_loop now waits up to 30 seconds for the heartbeat to return
site_numeric_id, then immediately pushes the menu snapshot and stats
before entering the regular poll loop. Previously the first push
wouldn't fire until 5 minutes after startup, making the menu
unavailable until then.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds _push_stats_snapshot() to cloud_sync.py. Every 5 minutes
(piggybacked on the existing _connect_loop push tick alongside the
menu snapshot) it queries the local DB and POSTs a JSON stats blob
to POST /api/remote/snapshot (site API key auth).
Stats collected:
- open_tables: count of open/partially_paid POS orders
- today_revenue: sum of active+paid item prices on orders
closed today
- today_orders: count of paid/closed POS orders today
- online_orders_pending: online orders awaiting acceptance
- online_orders_today: all online orders opened today
- current_shift: active waiter shift info (waiter_id, started_at)
if a business day is open; null otherwise
- as_of: UTC timestamp of the snapshot
The remote manager dashboard reads this via
GET /api/remote/sites/{id}/snapshot (manager JWT).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Phase 4 _mirror_status_to_cloud function had no way to look up the
cloud order's numeric id once it was marked synced (not in the pending
list anymore), so status updates from local staff could silently fail.
Fix:
- models/order.py: online_order_cloud_id INTEGER column added to Order
- main.py: migration for the new column
- schemas/order.py: online_order_cloud_id exposed in OrderOut
- cloud_sync.py: stores cloud_order["id"] as online_order_cloud_id
when creating the local order during the pull
- connect_orders.py: _mirror_status_to_cloud now takes the integer
cloud id directly — no more pending-list lookup; function body
reduced from ~50 lines to ~15
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.1 — cloud_sync.py
- _push_menu_snapshot(): serializes digital-visible products+categories
and POSTs to cloud /api/menu/sync every 5 minutes
- _pull_pending_orders(): polls cloud /api/orders/pending/{site_id}
every CONNECT_SYNC_INTERVAL_SECONDS (default 30s); creates local
Order + OrderItem rows, marks synced on cloud, broadcasts SSE event
- _connect_loop(): second asyncio task running the fast poll loop;
piggybacked menu push fires every 5 min regardless of poll interval
- _sync_once(): captures site_numeric_id from heartbeat response and
stores it in license_state so Connect loops can use it
- start_cloud_sync(): now creates and returns both tasks
4.2 — orders model/schema/migrations
- models/order.py: table_id made nullable (online orders have no
table); 7 new online_* columns added to Order
- schemas/order.py: OrderOut table_id Optional, all 7 online_* fields
added
- main.py: 8 additive ALTER TABLE migrations for orders table
4.3 — routers/connect_orders.py (NEW)
GET /api/connect/orders/incoming — pending online orders (any auth)
POST /api/connect/orders/{id}/accept — accept (manager)
POST /api/connect/orders/{id}/reject — reject with optional reason (manager)
POST /api/connect/orders/{id}/status — progress through lifecycle (manager)
All state changes mirror to cloud via background task and broadcast SSE
4.4 — main.py router registration
connect_orders router registered at /api/connect
config.py
CONNECT_SYNC_INTERVAL_SECONDS setting added (default 30)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend (reports.py):
- Cache order DB objects to avoid N+1 queries per log entry
- Track seen_order_totals keyed by order_id so reprinting the same
order doesn't inflate the total; returns total_amount pre-computed
Frontend (PrinterHistory.jsx):
- Remove client-side accumulation that caused the same issue; use
total_amount directly from the API response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- tables.py: PUT /api/tables/groups/reorder accepts an ordered list
of group IDs and updates sort_order accordingly
Frontend (TablesConfigTab):
- Zone tabs are now draggable for group tabs (not All/Ungrouped)
- HTML5 drag-and-drop with visual drop-target highlight and a
grab cursor; fires reorderGroups mutation on drop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds configurable buzzer control so the printer alerts kitchen staff
on each new ticket print.
Backend:
- printer_service.py: ESC 0x07 beep command fired before paper cut,
reads print.beep_on_ticket / print.beep_pattern settings; supports
single/double/triple/long presets and a custom:n1:n2:n3 format
- settings.py: registers the two new print.beep_* settings with
defaults (beep_on_ticket=true, beep_pattern=double)
- system.py: POST /api/system/printers/test-beep endpoint for
live testing from the dashboard
Frontend (PrintFontsTab):
- BeepSection component with preset grid, custom n1/n2/n3 inputs,
and a live test-beep button targeting any configured printer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds seven digital-menu-specific columns to the products table so
managers can control the public Xenia Connect menu independently of POS
data:
- digital_visible (INTEGER, default 1) — hide product from menu
- digital_available (INTEGER, default 1) — show as out-of-stock
- digital_name (VARCHAR, nullable) — override display name
- digital_description (VARCHAR, nullable)
- digital_price (REAL, nullable) — override price
- digital_discount (REAL, default 0.0) — percentage discount
- digital_image_url (VARCHAR, nullable) — menu-specific image
Changes:
- main.py: 7 additive ALTER TABLE migrations (safe on existing DBs)
- models/product.py: matching SQLAlchemy columns on Product
- schemas/product.py: ProductBase carries defaults (visible/available
default True); ProductUpdate has all fields Optional; ProductOut
inherits everything via ProductBase
No router changes needed — PUT /api/products/{id} picks up the new
fields from ProductUpdate automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>