feat: add payroll + tables served to shift detail and workday staff views

- business_day summary: waiter entries now include hourly_rate_snapshot, duration_hours,
  shift_pay (cumulative across day's shifts), tables_served (unique table count)
- ShiftDetailModal: 4-KPI row replaced with 2×4 grid —
  Row 1: Διάρκεια | Μισθός/Ώρα | Πληρωμή Βάρδιας | Αρχικά Μετρητά
  Row 2: Σύνολο Παραγγελιών | Εισπράξεις | Προς Παράδοση | Εξ. Τραπεζιών
- WorkdaySummaryModal WaitersTab: collapsed row uses ΠΑΡΑΓΓΕΛΙΕΣ | ΕΞ.ΤΡΑΠΕΖΙΩΝ |
  ΑΝΤΙΚ.ΕΙΣΠΡ. | ΕΙΣΠΡΑΞΗ; expanded grid is 2 rows:
  Row 1: ΕΝΑΡΞΗ | ΛΗΞΗ | ΜΙΣΘΟΣ/ΩΡΑ | ΜΙΣΘΟΣ (εως τώρα)
  Row 2: ΑΡΧΙΚΑ ΜΕΤΡΗΤΑ | ΤΑΜΕΙΟ ΤΩΡΑ | ΑΝΤΙΚ.ΕΙΣΠΡ. | ΕΞ.ΤΡΑΠΕΖΙΩΝ

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 21:24:01 +03:00
parent e5b8ef32be
commit b15fb27a37
3 changed files with 108 additions and 42 deletions

View File

@@ -246,14 +246,25 @@ def business_day_summary(
"shift_started_at": None,
"shift_ended_at": None,
"starting_cash": None,
# Phase 2B payroll
"hourly_rate_snapshot": None,
"duration_hours": None,
"shift_pay": None,
# distinct tables served (not order count)
"tables_served": 0,
}
return waiter_stats[uid]
# track unique tables per waiter (opened_by)
waiter_tables: dict = {} # uid → set of table_ids
for o in orders:
if o.status == "cancelled":
continue
e = _waiter_entry(o.opened_by)
e["orders_opened"] += 1
if o.table_id:
waiter_tables.setdefault(o.opened_by, set()).add(o.table_id)
for item in all_items:
if item.status == "cancelled":
@@ -281,6 +292,7 @@ def business_day_summary(
e["other"] += val
for shift in shifts:
from routers.shifts import compute_shift_pay
e = _waiter_entry(shift.waiter_id)
if e["shift_started_at"] is None or (shift.started_at and _dt(shift.started_at) < e["shift_started_at"]):
e["shift_started_at"] = _dt(shift.started_at)
@@ -290,6 +302,18 @@ def business_day_summary(
ended = _dt(shift.ended_at)
if e["shift_ended_at"] is None or ended > e["shift_ended_at"]:
e["shift_ended_at"] = ended
# payroll — take the most recent non-null snapshot
if shift.hourly_rate_snapshot is not None:
e["hourly_rate_snapshot"] = shift.hourly_rate_snapshot
pay_data = compute_shift_pay(shift)
e["duration_hours"] = round((e["duration_hours"] or 0) + pay_data["duration_hours"], 2)
if pay_data["shift_pay"] is not None:
e["shift_pay"] = round((e["shift_pay"] or 0) + pay_data["shift_pay"], 2)
# write tables_served counts
for uid, table_set in waiter_tables.items():
if uid in waiter_stats:
waiter_stats[uid]["tables_served"] = len(table_set)
# manager gets shift = day open/close
manager_opener = users_map.get(day.opened_by_id)