fix: report cancellation bugs + missing OrderItem model columns

Backend:
- OrderItem model: add cancelled_at, cancelled_by, cancel_reason columns
  (migration existed but model was missing them — caused 500 on cancellations_log)
- shifts.py _enrich_shift: count cancellations by quantity sum (not row count),
  add cancellation_value (sum of unit_price * quantity)
- reports.py current business day: add cancelled_items count (per-item quantity sum,
  across all orders in the day, not just fully-cancelled orders)

Manager dashboard:
- PrintFontsTab: fix SSE auth token key (access_token → manager_token, was causing 403)
- Today: show cancelled_items count as sub-label on Ακυρώσεις stat card
- OrderHistory: items/cancellations columns now use quantity sum, not row count
- WorkDaySummary drill-down: same quantity-sum fix
- ShiftsOverview: add Αξία Ακυρ. column next to Ακυρώσεις

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 17:15:55 +03:00
parent 36e9044972
commit 8533a1452b
8 changed files with 51 additions and 15 deletions

View File

@@ -1260,6 +1260,16 @@ def current_business_day(
p = db.query(Product).filter(Product.id == top_product_id).first()
top_product = {"id": top_product_id, "name": p.name if p else f"#{top_product_id}", "qty": item_counts[top_product_id]}
# Count cancelled items (individual items cancelled, across all orders in this day)
all_order_ids = [o.id for o in orders]
cancelled_items_qty = 0
if all_order_ids:
from sqlalchemy import func as sqlfunc
cancelled_items_qty = db.query(sqlfunc.sum(OrderItem.quantity)).filter(
OrderItem.order_id.in_(all_order_ids),
OrderItem.status == "cancelled",
).scalar() or 0
return {
"business_day": {
"id": day.id,
@@ -1272,6 +1282,7 @@ def current_business_day(
"orders_open": len(open_orders),
"active_waiters": len(active_shifts),
"cancellations": len(cancelled_orders),
"cancelled_items": int(cancelled_items_qty),
"top_product": top_product,
}
}

View File

@@ -75,7 +75,7 @@ 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
# Count cancelled items and their value attributed to this waiter during shift window
cancelled_q = db.query(OrderItem).filter(
OrderItem.status == "cancelled",
OrderItem.added_by == shift.waiter_id,
@@ -83,7 +83,9 @@ def _enrich_shift(shift: WaiterShift, db: Session) -> dict:
)
if shift.ended_at:
cancelled_q = cancelled_q.filter(OrderItem.added_at <= shift.ended_at)
cancellations = cancelled_q.count()
cancelled_rows = cancelled_q.all()
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 {
"id": shift.id,
"waiter_id": shift.waiter_id,
@@ -100,6 +102,7 @@ def _enrich_shift(shift: WaiterShift, db: Session) -> dict:
"duration_hours": pay_data["duration_hours"],
"shift_pay": pay_data["shift_pay"],
"cancellations": cancellations,
"cancellation_value": cancellation_value,
# Phase 2E
"counted_cash_end": shift.counted_cash_end,
"cash_discrepancy": shift.cash_discrepancy,