From 69220afb9ab44154943fcd5b1fd0693184b70b63 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Tue, 12 May 2026 16:46:11 -0400 Subject: [PATCH] Fix weekend day shift commands and admin 3CX routing - Add _find_employee_shift() to check both day/night on weekends - Drop/swap now correctly find and operate on weekend day shifts - Pick finds first available shift type on weekends - Admin override/open/clear update 3CX for same-day active shifts --- src/slack-bot/app.py | 85 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/slack-bot/app.py b/src/slack-bot/app.py index 9532a88..ce24457 100644 --- a/src/slack-bot/app.py +++ b/src/slack-bot/app.py @@ -100,6 +100,24 @@ def _is_active_shift_type(shift_type: str) -> bool: return shift_type == "night" +def _find_employee_shift(schedule, date_str, day_name, employee_ext): + """Find which shift type an employee is assigned to on a given date. + + On weekends, checks both day and night shifts. Returns (ext, name, source, shift_type) + or None if not found on any shift. + """ + if day_name in WEEKEND_DAYS: + for st in ("day", "night"): + ext, name, source = schedule.resolve_shift(date_str, day_name, st) + if ext == employee_ext: + return ext, name, source, st + return None + ext, name, source = schedule.resolve_shift(date_str, day_name, "night") + if ext == employee_ext: + return ext, name, source, "night" + return None + + def create_app( bot_token: str, signing_secret: str, schedule_channel: str | None = None ) -> App: @@ -380,7 +398,19 @@ def create_app( return day_name = date.strftime("%A") - ext, name, source = schedule.resolve_shift(date_str, day_name) + + # On weekends, find the first available shift (day then night) + shift_type = "night" + if day_name in WEEKEND_DAYS: + for st in ("day", "night"): + ext, name, source = schedule.resolve_shift(date_str, day_name, st) + if source == "available": + shift_type = st + break + else: + ext, name, source = schedule.resolve_shift(date_str, day_name) + else: + ext, name, source = schedule.resolve_shift(date_str, day_name) # Already assigned to someone else (not open) if source in ("weekly", "override") and ext != FALLBACK_EXTENSION: @@ -389,15 +419,22 @@ def create_app( ) return - schedule.set_override(date_str, employee["extension"], employee["name"]) + schedule.set_override( + date_str, employee["extension"], employee["name"], shift_type + ) - if is_today(date_str) and _is_active_shift_type("night"): + if is_today(date_str) and _is_active_shift_type(shift_type): _update_3cx_routing(employee["extension"]) respond(text=f"You picked up the shift for *{date.strftime('%A, %b %-d')}*.") blocks = build_shift_change_message( - user_id, date_str, "picked_up", employee["extension"], employee["name"] + user_id, + date_str, + "picked_up", + employee["extension"], + employee["name"], + shift_type=shift_type, ) try: client.chat_postMessage( @@ -435,22 +472,28 @@ def create_app( return day_name = date.strftime("%A") - ext, name, source = schedule.resolve_shift(date_str, day_name) + found = _find_employee_shift( + schedule, date_str, day_name, employee["extension"] + ) - if ext != employee["extension"]: + if not found: + ext, name, _source = schedule.resolve_shift(date_str, day_name) respond(text=f"That's not your shift — it belongs to {name} (Ext {ext}).") return - schedule.mark_open(date_str) + ext, name, source, shift_type = found + schedule.mark_open(date_str, shift_type) - if is_today(date_str) and _is_active_shift_type("night"): + if is_today(date_str) and _is_active_shift_type(shift_type): _update_3cx_routing(FALLBACK_EXTENSION) respond( text=f"You dropped the shift for *{date.strftime('%A, %b %-d')}*. It's now open for pickup." ) - blocks = build_shift_change_message(user_id, date_str, "dropped", ext, name) + blocks = build_shift_change_message( + user_id, date_str, "dropped", ext, name, shift_type=shift_type + ) try: client.chat_postMessage( channel=channel_id, blocks=blocks, text=f"Shift dropped for {date_str}" @@ -486,14 +529,19 @@ def create_app( return day_name = date.strftime("%A") - ext, name, source = schedule.resolve_shift(date_str, day_name) + found = _find_employee_shift( + schedule, date_str, day_name, employee["extension"] + ) - if ext != employee["extension"]: + if not found: + ext, name, _source = schedule.resolve_shift(date_str, day_name) respond( text=f"That's not your shift — it belongs to {name} (Ext {ext}). You can only swap your own shifts." ) return + _ext, _name, _source, shift_type = found + # Resolve target user — could be <@U12345> or an extension number target_text = parts[2].strip() slack_id_match = re.match(r"<@(\w+)(?:\|[^>]*)?>", target_text) @@ -511,9 +559,9 @@ def create_app( respond(text=f"Extension `{target_text}` not found in the roster.") return - schedule.set_override(date_str, target["extension"], target["name"]) + schedule.set_override(date_str, target["extension"], target["name"], shift_type) - if is_today(date_str) and _is_active_shift_type("night"): + if is_today(date_str) and _is_active_shift_type(shift_type): _update_3cx_routing(target["extension"]) blocks = build_shift_change_message( @@ -522,6 +570,7 @@ def create_app( "swapped", target["extension"], target["name"], + shift_type=shift_type, ) respond( text=f"Swapped *{date.strftime('%A, %b %-d')}* to {target['name']} (Ext {target['extension']})." @@ -572,6 +621,8 @@ def create_app( return date_str = date.strftime("%Y-%m-%d") schedule.set_override(date_str, employee["extension"], employee["name"]) + if is_today(date_str) and _is_active_shift_type("night"): + _update_3cx_routing(employee["extension"]) respond( text=f"Override set: *{date.strftime('%A, %b %-d')}* → {employee['name']} (Ext {ext})" ) @@ -587,6 +638,8 @@ def create_app( return date_str = date.strftime("%Y-%m-%d") schedule.mark_open(date_str) + if is_today(date_str) and _is_active_shift_type("night"): + _update_3cx_routing(FALLBACK_EXTENSION) respond(text=f"*{date.strftime('%A, %b %-d')}* marked as open.") _refresh_schedule_post(client) @@ -600,6 +653,12 @@ def create_app( return date_str = date.strftime("%Y-%m-%d") schedule.remove_override(date_str) + if is_today(date_str) and _is_active_shift_type("night"): + day_name = date.strftime("%A") + ext, _name, _source = schedule.resolve_shift( + date_str, day_name, "night" + ) + _update_3cx_routing(ext) respond( text=f"Override cleared for *{date.strftime('%A, %b %-d')}* — reverted to weekly schedule." )