diff --git a/src/shared/python/shared/ring_scheduler.py b/src/shared/python/shared/ring_scheduler.py index a3d4529..785fb15 100644 --- a/src/shared/python/shared/ring_scheduler.py +++ b/src/shared/python/shared/ring_scheduler.py @@ -24,8 +24,8 @@ def update_queue_routing( queue = client.get_queue(queue_number) client.update_queue_forwarding( queue_id=queue["Id"], - closed_destination=extension, - holiday_destination=extension, + closed=extension, + holiday=extension, ) logger.info("Updated queue %s to forward to Ext %s", queue_number, extension) return {"extension": extension, "queue": queue_number} diff --git a/src/shared/python/shared/three_cx_client.py b/src/shared/python/shared/three_cx_client.py index 15c26ee..7a2ee6f 100644 --- a/src/shared/python/shared/three_cx_client.py +++ b/src/shared/python/shared/three_cx_client.py @@ -84,6 +84,33 @@ class ThreeCXClient: resp.raise_for_status() return resp.json().get("value", []) + def _update_forwarding( + self, resource: str, resource_id: int, closed: str, holiday: str + ): + payload = { + "OutOfOfficeRoute": { + "IsPromptEnabled": False, + "Route": {"To": "Extension", "Number": closed, "External": ""}, + }, + "HolidaysRoute": { + "IsPromptEnabled": False, + "Route": {"To": "Extension", "Number": holiday, "External": ""}, + }, + } + resp = self.session.patch( + f"{self.base_url}/xapi/v1/{resource}({resource_id})", + json=payload, + ) + resp.raise_for_status() + logger.info( + "Updated %s %s: closed->Ext %s, holiday->Ext %s", + resource, + resource_id, + closed, + holiday, + ) + return resp.status_code + def get_ring_group(self, extension_number: str) -> dict: """Fetch ring group config by extension number.""" resp = self.session.get( @@ -93,42 +120,9 @@ class ThreeCXClient: return resp.json() def update_ring_group_forwarding( - self, - ring_group_id: int, - closed_destination: str, - holiday_destination: str, + self, ring_group_id: int, closed: str, holiday: str ): - """Update the OutOfOfficeRoute and HolidaysRoute on a ring group.""" - payload = { - "OutOfOfficeRoute": { - "IsPromptEnabled": False, - "Route": { - "To": "Extension", - "Number": closed_destination, - "External": "", - }, - }, - "HolidaysRoute": { - "IsPromptEnabled": False, - "Route": { - "To": "Extension", - "Number": holiday_destination, - "External": "", - }, - }, - } - resp = self.session.patch( - f"{self.base_url}/xapi/v1/RingGroups({ring_group_id})", - json=payload, - ) - resp.raise_for_status() - logger.info( - "Updated ring group %s: closed->Ext %s, holiday->Ext %s", - ring_group_id, - closed_destination, - holiday_destination, - ) - return resp.status_code + return self._update_forwarding("RingGroups", ring_group_id, closed, holiday) def get_queue(self, extension_number: str) -> dict: """Fetch queue config by extension number.""" @@ -138,40 +132,5 @@ class ThreeCXClient: resp.raise_for_status() return resp.json() - def update_queue_forwarding( - self, - queue_id: int, - closed_destination: str, - holiday_destination: str, - ): - """Update the OutOfOfficeRoute and HolidaysRoute on a queue.""" - payload = { - "OutOfOfficeRoute": { - "IsPromptEnabled": False, - "Route": { - "To": "Extension", - "Number": closed_destination, - "External": "", - }, - }, - "HolidaysRoute": { - "IsPromptEnabled": False, - "Route": { - "To": "Extension", - "Number": holiday_destination, - "External": "", - }, - }, - } - resp = self.session.patch( - f"{self.base_url}/xapi/v1/Queues({queue_id})", - json=payload, - ) - resp.raise_for_status() - logger.info( - "Updated queue %s: closed->Ext %s, holiday->Ext %s", - queue_id, - closed_destination, - holiday_destination, - ) - return resp.status_code + def update_queue_forwarding(self, queue_id: int, closed: str, holiday: str): + return self._update_forwarding("Queues", queue_id, closed, holiday) diff --git a/src/slack-bot/app.py b/src/slack-bot/app.py index 6642fbd..2ee82ea 100644 --- a/src/slack-bot/app.py +++ b/src/slack-bot/app.py @@ -377,9 +377,11 @@ def create_app( respond(text=f"Linked your account to *{employee['name']}* (Ext {ext}).") def _handle_pick(respond, schedule, user_id, text, channel_id, client): - parts = text.split(maxsplit=1) + parts = text.split() if len(parts) < 2: - respond(text="Usage: `/oncall pick ` (e.g. `/oncall pick friday`)") + respond( + text="Usage: `/oncall pick [day|night]` (e.g. `/oncall pick friday`)" + ) return employee = schedule.get_employee_by_slack_id(user_id) @@ -389,10 +391,14 @@ def create_app( ) return - date = parse_date(parts[1]) + explicit_shift = ( + parts[2] if len(parts) > 2 and parts[2] in ("day", "night") else None + ) + date_text = parts[1] + date = parse_date(date_text) if not date: respond( - text=f"Couldn't parse date: `{parts[1]}`. Try: today, tomorrow, friday, 4/5, 2026-04-05" + text=f"Couldn't parse date: `{date_text}`. Try: today, tomorrow, friday, 4/5, 2026-04-05" ) return @@ -403,9 +409,11 @@ def create_app( day_name = date.strftime("%A") - # On weekends, find the first available shift (day then night) - shift_type = "night" - if day_name in WEEKEND_DAYS: + if explicit_shift: + shift_type = explicit_shift + ext, name, source = schedule.resolve_shift(date_str, day_name, shift_type) + elif day_name in WEEKEND_DAYS: + shift_type = "night" for st in ("day", "night"): ext, name, source = schedule.resolve_shift(date_str, day_name, st) if source == "available": @@ -414,6 +422,7 @@ def create_app( else: ext, name, source = schedule.resolve_shift(date_str, day_name) else: + shift_type = "night" ext, name, source = schedule.resolve_shift(date_str, day_name) # Already assigned to someone else (not open)