mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-10-06 13:32:04 +00:00
The pickup button handler used an unconditional put_item, so concurrent clicks would both succeed with last-write-wins. Added claim_open_shift() which uses a DynamoDB ConditionExpression to only write if the shift is still OPEN. The button handler now returns an ephemeral "already taken" message when the condition fails. Closes #19
This commit is contained in:
parent
fe6b780e12
commit
1e90e0eaa2
2 changed files with 29 additions and 2 deletions
10
src/app.py
10
src/app.py
|
|
@ -124,7 +124,6 @@ def create_app(bot_token: str, signing_secret: str) -> App:
|
||||||
def handle_pickup_button(ack, body, client):
|
def handle_pickup_button(ack, body, client):
|
||||||
ack()
|
ack()
|
||||||
action_id = body["actions"][0]["action_id"]
|
action_id = body["actions"][0]["action_id"]
|
||||||
# Parse action_id: pickup_2026-04-12 or pickup_2026-04-12_day
|
|
||||||
remainder = action_id.replace("pickup_", "")
|
remainder = action_id.replace("pickup_", "")
|
||||||
if remainder.endswith("_day"):
|
if remainder.endswith("_day"):
|
||||||
date_str = remainder[:-4]
|
date_str = remainder[:-4]
|
||||||
|
|
@ -145,7 +144,14 @@ def create_app(bot_token: str, signing_secret: str) -> App:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
schedule.set_override(date_str, employee["extension"], employee["name"], shift_type)
|
claimed = schedule.claim_open_shift(date_str, employee["extension"], employee["name"], shift_type)
|
||||||
|
if not claimed:
|
||||||
|
client.chat_postEphemeral(
|
||||||
|
channel=channel_id,
|
||||||
|
user=user_id,
|
||||||
|
text=f"That shift on *{date_str}* was already picked up by someone else.",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if is_today(date_str) and shift_type == "night":
|
if is_today(date_str) and shift_type == "night":
|
||||||
invoke_3cx_scheduler(employee["extension"])
|
invoke_3cx_scheduler(employee["extension"])
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,27 @@ class ShiftSchedule:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def claim_open_shift(self, date_str: str, extension: str, name: str, shift_type: str = "night") -> bool:
|
||||||
|
"""Atomically claim a shift only if it is currently OPEN.
|
||||||
|
|
||||||
|
Returns True if the claim succeeded, False if someone else already took it.
|
||||||
|
"""
|
||||||
|
sk = f"{date_str}-DAY" if shift_type == "day" else date_str
|
||||||
|
try:
|
||||||
|
self.table.put_item(
|
||||||
|
Item={
|
||||||
|
"PK": "OVERRIDE",
|
||||||
|
"SK": sk,
|
||||||
|
"extension": extension,
|
||||||
|
"name": name,
|
||||||
|
},
|
||||||
|
ConditionExpression="extension = :open",
|
||||||
|
ExpressionAttributeValues={":open": "OPEN"},
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except self.table.meta.client.exceptions.ConditionalCheckFailedException:
|
||||||
|
return False
|
||||||
|
|
||||||
def mark_open(self, date_str: str, shift_type: str = "night") -> None:
|
def mark_open(self, date_str: str, shift_type: str = "night") -> None:
|
||||||
sk = f"{date_str}-DAY" if shift_type == "day" else date_str
|
sk = f"{date_str}-DAY" if shift_type == "day" else date_str
|
||||||
self.table.put_item(
|
self.table.put_item(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue