- New models: SiteNote, SiteTodo (site_notes, site_todos tables)
- New schemas: NoteOut, TodoOut with creator/done-by name enrichment
- New router: /api/notes/ — full CRUD for notes and todos
- Notes: create, list (pinned first), update (body + pin), delete
- Todos: create, list (undone high-priority first), toggle done, edit, delete
- Marking done records done_at + done_by_id; unchecking clears both
- Migrations: CREATE TABLE IF NOT EXISTS for both tables (additive, safe)
- NotesPage.jsx: two-column layout — notes left, todos right
- Notes: inline click-to-edit, pin toggle, Ctrl+Enter to save, pinned section on top
- Todos: inline edit, high-priority flag, completed collapse toggle
- /notes route added to App.jsx
- NotebookPen sidebar entry between Διαχείριση and Ρυθμίσεις
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Products: add cost_simple (flat €) and cost_breakdown (JSON line items)
- OrderItems: add unit_cost snapshot written at time of order creation
- Snapshot logic: breakdown sum takes priority over cost_simple; NULL if neither set
- Product schema: serialize/deserialize cost_breakdown as JSON; expose in API
- Product performance report: add trackable_profit, uncosted_revenue, has_gap per product
- Manager UI: cost section in product edit form (simple / detailed toggle with live margin %)
- Products list: show margin % or cost-error badge per product
- Product performance report: profit column + gap warning banner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New reservations module: model, schema, router (CRUD + status updates + upcoming alerts)
and background task for auto-expiring stale reservations
- Reports: print_products, print_categories, print_tables analytics endpoints
plus meta_products and business_day_summary for workday close/view flow
- printer_service: configurable font sizes/weights, donut/bar chart print layout helpers,
analytics print blocks per printer
- tables/schemas: surfaced color, zone, and other new fields on Table, Product, User, Printer
- demo_seed.py for quick dev DB population; wipe_database.py utility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- connect_orders.py: add GET /api/connect/orders?status=active|history endpoints
- api/client.js: add getIncomingOrders, getActiveOrders, getOrderHistory,
acceptOnlineOrder, rejectOnlineOrder, updateOnlineOrderStatus
- OnlineOrdersPage.jsx: three-tab page (Incoming / Active / History) with
order cards, action buttons (accept/reject/status progression),
20 s polling, SSE listener for online_order_received/updated events,
and optimistic UI updates
- App.jsx: add /online-orders route
- Sidebar.jsx: add Online Orders nav item with red badge showing pending count,
polling every 20 s independently of the page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_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>