- Add tz.py with local_strftime/to_local helpers that read system.timezone
from DB and convert UTC datetimes to venue local time before formatting
- Fix all strftime() calls in orders.py, reports.py, printer_service.py
that were formatting UTC datetimes without timezone conversion
- Fix get_order endpoint returning raw dicts without Z suffix on datetimes,
causing JS new Date() to treat timestamps as local instead of UTC
- Fix fmtDate() in tokens.js that stripped the T separator before parsing,
breaking UTC-to-local conversion for all report date displays
- Make open/partially_paid table chips more visually distinct on dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend reports: fix cost/profit calculation to handle null unit_cost
with has_gap flag; expose total_cost in product & workday summary
- StaffTab: add hourly_rate field in payroll section (admin-only)
- ProductFormModal: major refactor of form layout and structure
- ProductsTab: minor tweaks aligned with form changes
- Report pages (Today, WorkDaySummary, CategoryPerformance,
ProductPerformance, RevenueTrends): UI improvements and cost data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- usePhase2Features.js hook: manages localStorage-based feature flags for all 8 Phase 2
sidebar entries (notes, expenses, contacts, customers, tabs, waste, kds, schedule)
All default to enabled=true. setFeatureEnabled() writes to localStorage and dispatches
a 'phase2features' event so all subscribers re-render immediately without page reload
- Phase2FeaturesTab.jsx: new Settings tab 'Λειτουργίες' — toggle each Phase 2 page on/off
with master 'Ενεργοποίηση όλων' / 'Απενεργοποίηση όλων' buttons
Note shown: changes are localStorage-only, instant, no reload needed, no data deleted
- SettingsPage.jsx: adds 'Λειτουργίες' and reinstates 'Developer' tab (was missing import)
- Sidebar.jsx: subscribes to phase2features + storage events; filters ALL_NAV to remove
disabled Phase 2 entries; each phase2 item has a phase2 id that isFeatureEnabled() checks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- GET /api/reports/discounts: read-only audit of all OrderDiscount records
Filters: ?from=&to= or ?business_day_id=, ?applied_by=
Returns:
- discounts[]: id, order_id, table_name, applied_by_name, applied_at, discount_type,
discount_value, discount_amount (computed euro value), order_total_before,
item_id (null = whole-order), reason
- total_discount_value: sum of all euro discount amounts
- order_count: unique orders that received a discount
- by_waiter[]: grouped summary (waiter_name, count, total_value)
discount_amount computation: fixed → discount_value directly;
percent → computed from order or item total at query time
Frontend:
- DiscountsLog.jsx: read-only report under Reports → Λειτουργίες → Εκπτώσεις
- Filter bar: range/workday toggle, date range, waiter filter
- 3 stat cards: total discount value, orders with discounts, count of entries
- By-waiter summary table (hidden when only one waiter)
- Full audit table: date, order + table, waiter, type badge (% blue / € amber),
order total before, euro amount discounted in amber, reason
- Totals footer row
- No create/edit/delete — pure audit log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend — /api/business-day/summary now returns full financial summary:
- COGS: trackable cost (sum of unit_cost snapshots on paid items), uncosted item count
and revenue, gross_profit, cogs_has_gap flag
- Labor: total_labor_cost (sum of shift_pay for the day), labor_untracked_shifts count
- Waste: waste_item_count + waste_estimated_cost from waste_log for this business day
- Expenses: expenses_total, expenses_paid_today, expenses_due for expenses logged today
- Tabbed revenue: revenue from items with status='tabbed' (deferred, not yet collected)
- Net estimate: revenue - COGS - labor - waste - expenses_paid; net_has_unknowns flag
when any component has gaps (uncosted items or untracked shifts)
- compute_shift_pay import hoisted out of loop (bug fix)
Frontend — WorkdaySummaryModal OverviewTab fully expanded:
- New FinancialRow helper for consistent two-column P&L rows with accent/warn/indent
- REVENUE section: total sales, tabbed deduction, net collected
- COGS section: trackable cost, uncosted items warning, gross profit with gap indicator
- LABOR section: tracked labor cost, untracked shifts warning
- WASTE section: item count + estimated cost (hidden when zero)
- EXPENSES section: total, paid today, still due (hidden when zero)
- NET ESTIMATE: prominent bottom line, amber warning when unknowns exist
- Payment breakdown bar (existing)
- Cash reconciliation summary when store cash was counted
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New model: ScheduledShift (scheduled_shifts table)
Planning data separate from waiter_shifts (actual data) — linked conceptually by user+date
start_time/end_time stored as "HH:MM" strings (SQLite has no native Time type)
- New router /api/schedule/:
- GET / — list by date range + optional user_id filter
- POST / — create scheduled shift (validates user exists)
- PUT /{id} — update times/notes
- DELETE /{id} — remove
- GET /week?week_start= — full week view: scheduled shifts with actual WaiterShift
comparison for the same user+date, estimated weekly labor cost, waiters list with rates
- Each ScheduledShiftOut includes duration_hours (handles overnight) + estimated_pay
(duration × hourly_rate, null if rate not set)
- Migration: CREATE TABLE IF NOT EXISTS scheduled_shifts
Frontend:
- SchedulePage (/schedule): weekly calendar grid — rows = staff, columns = Mon–Sun
- Week navigation (← Προηγ. / Αυτή η εβδομάδα / Επόμ. →)
- Estimated weekly labor cost in header when shifts have rates
- ShiftSlot cells: blue = scheduled only, green = scheduled + actual shift happened
Shows scheduled times + actual start/end from real WaiterShift data
- Click empty cell → AddShiftModal (pre-selects that row's waiter, any date in week)
- "+" row at bottom for waiters not yet scheduled this week
- Delete button (✕) per slot with confirm
- Legend explaining the color scheme
- Sidebar: CalendarDays icon for Πρόγραμμα (after KDS)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New router /api/kds/:
- GET /items — returns all active order items for open orders, grouped by printer zone
(zone_id, zone_name, items array with table_name, product_name, qty, notes, added_at)
Zones sorted by zone_id asc, no-zone column last
- PUT /orders/{order_id}/items/{item_id}/status — mark item 'ready' (active→ready only)
Broadcasts item_status_changed SSE event to all connected clients
- No migration needed — 'ready' is a new valid string value for order_items.status
- pay_items endpoint: now accepts 'ready' items as payable (status.in_(['active','ready']))
- active_remaining count for order status also includes 'ready' items
Frontend:
- KdsPage (/kds): full-screen dark layout, columns per printer zone
- Zone columns: dark header with zone name + pending count, scrollable item cards
- Item cards: table name badge, elapsed time (colour-coded: green<8m, amber<15m, red>=15m),
product name + quantity, notes in amber, tap anywhere to mark ready
- Optimistic marking: card dims while request in flight, disappears on success
- SSE live updates: listens for order_updated / item_status_changed / order_paid / order_closed
- Fallback poll every 30s; manual ↻ Ανανέωση button; live clock in top bar
- Empty state: ✓ message when no pending items
- Sidebar: ChefHat icon for KDS (after Αποβλήτα)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New model: WasteLog (waste_log table) — product, quantity (float), reason, reason_notes,
unit_cost_snapshot (copied at log time from product's effective cost), total_cost (stored),
logged_by, business_day_id
- Cost snapshot: same logic as Phase 2A — breakdown sum first, fallback to cost_simple, null if neither
- New router /api/waste/:
- GET / — list entries (?business_day_id= or ?from=&to=), ordered by logged_at desc
- POST / — log waste; snapshots cost; attaches to open business day automatically
- DELETE /{id} — manager only; only allowed within the same open business day
- GET /summary — totals by reason + by product for a period
- product_performance report: waste_qty + waste_cost added per product from the same period;
products with only waste (no sales) also included
- Migration: CREATE TABLE IF NOT EXISTS waste_log
Frontend:
- WastePage (/waste): fast log form (product picker, quantity, reason chips, notes);
today's entries list below (scoped to active business day); cost shown per entry;
delete button for today's entries; history section behind toggle with date-range picker
- ProductPerformance report: Απόβλητα column showing waste_qty + cost in amber
- Sidebar: Trash2 icon for Αποβλήτα (after Καρτέλες)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New models: Tab, TabEntry, TabPayment (3 new tables)
- Tab: open|closed|forgiven, one open tab per customer enforced
- TabEntry: what went ON the tab (per-item snapshot with description)
- TabPayment: what came OFF the tab; balance = sum(entries) - sum(payments), never stored
- New router /api/tabs/:
- GET / — list open tabs (sorted by balance desc); ?tab_status= override
- GET /{id} — full tab detail with entries + payments
- POST / — open new tab for a customer (400 if one already exists)
- POST /{id}/entries — add entry directly (amount + description)
- POST /{id}/pay — record payment; validates amount <= balance
- POST /{id}/close — close tab (requires balance = 0)
- POST /{id}/forgive — write off remaining balance
- GET /customer/{customer_id} — all tabs for a customer (open first)
- POST /api/orders/{id}/items/{item_id}/tab:
- Requires order has a customer assigned
- Finds or auto-creates open tab for that customer
- Sets order_item.status = "tabbed" (new valid status value)
- Creates TabEntry with auto-generated description
- Broadcasts order_updated SSE event
- Migrations: CREATE TABLE IF NOT EXISTS for tabs, tab_entries, tab_payments
Frontend:
- TabsPage (/tabs): open tabs list sorted by balance; each card shows charges/payments
history, live balance, Πληρωμή modal (defaults to full balance), Κλείσιμο button
(only shown when balance=0), Χάρισμα Υπολοίπου with confirm modal + reason
- CustomersPage CustomerDetail: open tab banner showing balance + entry count
(appears between stats and contact info when customer has an open tab)
- OrderDetailPage: 📋 Καρτέλα button appears on active items when order has a customer;
tabbed items show a 📋 Καρτέλα badge; tabItem mutation calls /items/{id}/tab
- Sidebar: CreditCard icon for Καρτέλες (after Πελάτες)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New model: Customer (customers table) — name, nickname, phone, email, notes, is_active
- Order model: add customer_id nullable FK → customers
- New router: /api/customers/ — list (with ?search=), create, get, update, soft-delete,
GET /{id}/orders (visit history with totals)
- Each CustomerOut includes visit_count + total_spent (computed from closed/paid orders)
- PUT /api/orders/{id}/customer — assign or unassign a customer; manager-only;
validates customer is active; broadcasts order_updated SSE event
- GET /api/orders/{id} now returns customer_id, customer_name (with nickname), customer_phone
- Migration: CREATE TABLE customers + ALTER TABLE orders ADD COLUMN customer_id
Frontend:
- CustomersPage (/customers): searchable list (name/nickname/phone), avatar initials,
visit count + total_spent on each row; click → detail panel slides in showing stats
(visits, total spent, avg ticket), contact info, notes, full visit history
- OrderDetailPage: new Πελάτης card — shows assigned customer in blue if set;
for open orders shows "+ Ανάθεση πελάτη" button → inline search dropdown → assign;
Αφαίρεση button to unassign
- Sidebar: Users icon for Πελάτες (between Επαφές and Ρυθμίσεις)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New models: Contact, Expense, ExpensePayment (3 new tables)
- Expense status (paid/partial/due) is always computed from paid_amount vs total_amount — never stored
- paid_amount recomputed from sum of all payments on each payment write (no client trust)
- New router /api/contacts/: list, create, update, soft-delete (is_active=false)
- New router /api/expenses/: list (filter by status/category/contact), create, update, delete,
record payment, summary (total_due, total_paid, by_category)
- Migrations: CREATE TABLE IF NOT EXISTS for contacts, expenses, expense_payments
Frontend:
- ContactsPage (/contacts): searchable table, type badge (Προμηθευτής/Προσωπικό/Κοινή Χρεία/Άλλο),
create/edit modal, soft-delete
- ExpensesPage (/expenses): filter bar (status + category), expandable rows with payment history,
summary header showing total outstanding, inline Payment modal (defaults to full due amount),
create/edit expense modal
- Sidebar: Receipt icon for Έξοδα, BookUser icon for Επαφές
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
Adds a new "Digital Menu" tab with three sections:
- Visibility: digital_visible / digital_available toggles
- Display overrides: digital_name, digital_description, digital_image_url
- Pricing: digital_price / digital_discount with live customer-price preview
All 7 fields are wired into form state (buildFormFromProduct) and
included in the PUT request body (buildBody).
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>
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>