From e28f5918168b5097d8276bb0543dffe9f172a93f Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:15:41 -0400 Subject: [PATCH] Add /oncall rate command to manage shift pay rates from Slack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /oncall rate — view current default and per-person rates - /oncall rate default — set the global fallback rate - /oncall rate — set a person-specific rate --- src/app.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ src/blocks.py | 3 +++ src/schedule.py | 16 +++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/app.py b/src/app.py index 8d0a303..962d263 100644 --- a/src/app.py +++ b/src/app.py @@ -105,6 +105,8 @@ def create_app(bot_token: str, signing_secret: str) -> App: respond(blocks=build_roster_blocks(schedule.get_roster())) elif text == "pay": _show_pay(respond, schedule) + elif text.startswith("rate"): + _handle_rate(respond, schedule, text) elif text.startswith("register"): _handle_register(respond, schedule, user_id, text) elif text.startswith("pick"): @@ -177,6 +179,57 @@ def create_app(bot_token: str, signing_secret: str) -> App: else: respond(text=f"No pay record found for the week of {prev_monday.strftime('%b %-d')}.") + def _handle_rate(respond, schedule, text): + parts = text.split() + # /oncall rate — show current rates + if len(parts) == 1: + default_rate = schedule.get_shift_rate() + roster = schedule.get_roster() + lines = [f"*Default rate:* ${default_rate:.2f}/shift\n"] + custom = [(e["SK"], e.get("name", "Unknown"), float(e["shift_rate"])) for e in roster if e.get("shift_rate")] + if custom: + lines.append("*Per-person rates:*") + for ext, name, rate in sorted(custom, key=lambda x: x[0]): + lines.append(f"• {name} (Ext {ext}) — ${rate:.2f}/shift") + else: + lines.append("_No per-person rates set — everyone uses the default._") + respond(text="\n".join(lines)) + return + + # /oncall rate default + if parts[1] == "default": + if len(parts) < 3: + respond(text="Usage: `/oncall rate default ` (e.g. `/oncall rate default 50`)") + return + try: + amount = float(parts[2].replace("$", "")) + except ValueError: + respond(text=f"Invalid amount: `{parts[2]}`") + return + schedule.set_default_shift_rate(amount) + respond(text=f"Default shift rate set to *${amount:.2f}*.") + return + + # /oncall rate + if len(parts) < 3: + respond(text="Usage: `/oncall rate ` (e.g. `/oncall rate 114 75`)") + return + + ext = parts[1] + employee = schedule.get_employee_by_extension(ext) + if not employee: + respond(text=f"Extension `{ext}` not found in the roster.") + return + + try: + amount = float(parts[2].replace("$", "")) + except ValueError: + respond(text=f"Invalid amount: `{parts[2]}`") + return + + schedule.set_employee_shift_rate(ext, amount) + respond(text=f"Shift rate for *{employee['name']}* (Ext {ext}) set to *${amount:.2f}*.") + def _handle_register(respond, schedule, user_id, text): parts = text.split() if len(parts) < 2: diff --git a/src/blocks.py b/src/blocks.py index c8b5998..40882c7 100644 --- a/src/blocks.py +++ b/src/blocks.py @@ -113,6 +113,9 @@ def build_help_blocks() -> list[dict]: "`/oncall swap @person` — Swap your shift with someone\n" "`/oncall register ` — Link your Slack account to your extension\n" "`/oncall pay` — Show last week's pay summary\n" + "`/oncall rate` — Show current shift rates\n" + "`/oncall rate default ` — Set the default rate\n" + "`/oncall rate ` — Set a person's rate\n" "`/oncall roster` — Show all registered employees\n" "`/oncall help` — Show this help message\n\n" "_Dates can be: today, tomorrow, monday, friday, 4/5, 2026-04-05_" diff --git a/src/schedule.py b/src/schedule.py index b98351a..84d4450 100644 --- a/src/schedule.py +++ b/src/schedule.py @@ -127,6 +127,22 @@ class ShiftSchedule: resp = self.table.get_item(Key={"PK": "CONFIG", "SK": "CONFIG"}) return resp.get("Item", {}) + def set_default_shift_rate(self, rate: float) -> None: + """Set the global default shift rate in config.""" + self.table.update_item( + Key={"PK": "CONFIG", "SK": "CONFIG"}, + UpdateExpression="SET shift_rate = :r", + ExpressionAttributeValues={":r": str(rate)}, + ) + + def set_employee_shift_rate(self, extension: str, rate: float) -> None: + """Set a per-person shift rate on a roster entry.""" + self.table.update_item( + Key={"PK": "ROSTER", "SK": extension}, + UpdateExpression="SET shift_rate = :r", + ExpressionAttributeValues={":r": str(rate)}, + ) + def get_shift_rate(self, extension: str | None = None) -> float: """Get the per-shift pay rate.