feat: ShiftDetailModal rework, Today cancellation events, shift details icon button

- Backend: add cancellation_events (row count) to _enrich_shift and shift summary
- Backend: add cancellation_events field to current business day endpoint
- Today tab: hero number now shows cancellation_events (distinct cancel actions),
  sub-label shows 'X παραγγελίες / Y είδη'
- ShiftsOverview: Λεπτομέρειες button is now icon-only (Eye, light blue) matching
  the delete button style
- ShiftDetailModal: modal widened to 1080px; ΠΑΡΑΔΟΘΗΚΑΝ KPI replaced with
  Ακυρώσεις showing 'X events / Y items'; all filter labels renamed (Πλήρης
  Παραγγελία, Μόνο Πληρώθηκε, Μόνο Παρήγγειλε, Ανοιχτό ακόμη); new Ακυρώθηκε
  filter added; cancelled items shown in red, Πληρώθηκε line hidden for cancelled;
  legend updated; classify() handles cancelled status before other checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 17:48:30 +03:00
parent 8533a1452b
commit 02ec1aa28f
5 changed files with 120 additions and 65 deletions

View File

@@ -84,6 +84,7 @@ def _enrich_shift(shift: WaiterShift, db: Session) -> dict:
if shift.ended_at:
cancelled_q = cancelled_q.filter(OrderItem.added_at <= shift.ended_at)
cancelled_rows = cancelled_q.all()
cancellation_events = len(cancelled_rows)
cancellations = sum(r.quantity for r in cancelled_rows)
cancellation_value = round(sum(r.unit_price * r.quantity for r in cancelled_rows), 2)
return {
@@ -101,6 +102,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"],
"cancellation_events": cancellation_events,
"cancellations": cancellations,
"cancellation_value": cancellation_value,
# Phase 2E
@@ -417,7 +419,7 @@ def get_shift_summary(
waiter_id = shift.waiter_id
# Collect all relevant items: paid in this shift OR added by this waiter
# Collect all relevant items: paid in this shift OR added by this waiter (any status)
paid_items = db.query(OrderItem).options(
joinedload(OrderItem.product),
joinedload(OrderItem.order),
@@ -446,6 +448,22 @@ def get_shift_summary(
items_by_id[item.id] = item
all_items = list(items_by_id.values())
# Also include items cancelled within this shift window (may not be in ordered_items
# if they were cancelled after the shift ended or deduped out)
cancelled_items_for_summary = db.query(OrderItem).options(
joinedload(OrderItem.product),
joinedload(OrderItem.order),
).filter(
OrderItem.added_by == waiter_id,
OrderItem.status == "cancelled",
OrderItem.added_at >= shift.started_at,
OrderItem.added_at <= upper_bound,
).all()
for item in cancelled_items_for_summary:
if item.id not in items_by_id:
items_by_id[item.id] = item
all_items = list(items_by_id.values())
# Build lookup maps
all_waiter_ids = set()
for item in all_items: