feat: Feature 2 — waiter order cancellation + report updates

Backend:
- Add can_cancel_orders to User model and schema
- Add global orders.waiter_cancellations_allowed setting (migration)
- Cancel endpoints: mark items with cancelled_by/cancelled_at, fire cancellation print
- print_cancellation_ticket: routes to same printer zones, prints ΑΚΥΡΩΣΗ banner
- Fix cancellations_log: date filter, waiter filter, join syntax
- shift/orders: add cancellations count and hours_worked per waiter
- _enrich_shift: add cancellations count to shift data
- Add cancel-permissions endpoint for PWA

Manager dashboard:
- Global cancel setting toggle in Settings > Operation > Shift Settings
- Per-waiter can_cancel_orders checkbox in staff modal
- Manager cancel flow: print confirmation prompt (Ναι/Όχι) in DashboardPage
- ShiftsOverview: Ακυρώσεις column per shift
- Activity: multi-bar chart with ORDERS/ITEMS/CANCELLATIONS/ΕΣΟΔΑ/ΩΡΕΣ checkboxes,
  grouped/stacked switch, right X-axis for hours, full waiter name on hover
- OrderHistory: cancelled items count column per order
- WorkDaySummary drill-down: cancelled items column in orders tab

Waiter PWA:
- Replace 3 pills with CLEAR | ALL | ACTIONS
- ACTIONS opens ItemActionModal for selected items
- ItemActionModal: ORDER AGAIN, MOVE TO OTHER TABLE, SPLIT, CANCEL ORDER
- ActionsSheet: Cancel Παραγγελίας option (greyed if no permission)
- CancelConfirmModal: requires confirmation before cancelling
- TableListPage: cancel order from long-press quick modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 12:47:02 +03:00
parent b5b647422a
commit 8a5a6f8be9
16 changed files with 855 additions and 108 deletions

View File

@@ -75,6 +75,15 @@ def _enrich_shift(shift: WaiterShift, db: Session) -> dict:
wname = (w.full_name or w.username) if w else f"#{shift.waiter_id}"
total = compute_shift_total(shift.id, db) if shift.ended_at is None else (shift.total_collected or 0.0)
pay_data = compute_shift_pay(shift)
# Count cancelled items attributed to this waiter during shift window
cancelled_q = db.query(OrderItem).filter(
OrderItem.status == "cancelled",
OrderItem.added_by == shift.waiter_id,
OrderItem.added_at >= shift.started_at,
)
if shift.ended_at:
cancelled_q = cancelled_q.filter(OrderItem.added_at <= shift.ended_at)
cancellations = cancelled_q.count()
return {
"id": shift.id,
"waiter_id": shift.waiter_id,
@@ -90,6 +99,7 @@ def _enrich_shift(shift: WaiterShift, db: Session) -> dict:
"hourly_rate_snapshot": shift.hourly_rate_snapshot,
"duration_hours": pay_data["duration_hours"],
"shift_pay": pay_data["shift_pay"],
"cancellations": cancellations,
# Phase 2E
"counted_cash_end": shift.counted_cash_end,
"cash_discrepancy": shift.cash_discrepancy,