mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-10-07 16:19:00 +00:00
fix(pay): skip duplicate checkcomponents send on weekly-post retry
This commit is contained in:
parent
8d0a9850c6
commit
8bf45f73ee
4 changed files with 71 additions and 14 deletions
|
|
@ -17,7 +17,8 @@ emails payroll and DMs the pay summary as before. After those go out, it also
|
|||
sends last week's after-hours dollar lines (by extension, previous Monday
|
||||
through Sunday) to the paychex-integrations checkcomponents queue. Unassigned
|
||||
fallback extension 100 and $0 totals are left out. A queue failure does not
|
||||
block the Monday schedule post.
|
||||
block the Monday schedule post. A retry or forced re-run of the same week does
|
||||
not send the lines twice.
|
||||
|
||||
## v1.14.0 — July 2, 2026
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ emails payroll and DMs the pay summary as before. After those go out, it also
|
|||
sends last week's after-hours dollar lines (by extension, previous Monday
|
||||
through Sunday) to the paychex-integrations checkcomponents queue. Unassigned
|
||||
fallback extension 100 and $0 totals are left out. A queue failure does not
|
||||
block the Monday schedule post.
|
||||
block the Monday schedule post. A retry or forced re-run of the same week does
|
||||
not send the lines twice.
|
||||
|
||||
## v1.14.0 — July 2, 2026
|
||||
|
||||
|
|
|
|||
|
|
@ -277,16 +277,18 @@ def build_checkcomponents_payload(pay_record: dict) -> dict:
|
|||
}
|
||||
|
||||
|
||||
def _send_checkcomponents(pay_record: dict) -> None:
|
||||
def _send_checkcomponents(pay_record: dict) -> bool:
|
||||
"""Enqueue sibling dollar lines. Returns True if a message was sent."""
|
||||
queue_url = os.environ.get("CHECKCOMPONENTS_QUEUE_URL", "").strip()
|
||||
if not queue_url:
|
||||
return
|
||||
return False
|
||||
payload = build_checkcomponents_payload(pay_record)
|
||||
if not payload["lines"]:
|
||||
return
|
||||
return False
|
||||
boto3.client("sqs").send_message(
|
||||
QueueUrl=queue_url, MessageBody=json.dumps(payload)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
|
|
@ -313,8 +315,11 @@ def handler(event, context):
|
|||
|
||||
pay_dm_user = os.environ.get("PAY_REPORT_USER")
|
||||
|
||||
week_key = prev_monday.strftime("%Y-%m-%d")
|
||||
if pay_record["breakdown"]:
|
||||
week_key = prev_monday.strftime("%Y-%m-%d")
|
||||
existing = schedule.get_pay_record(week_key)
|
||||
if existing and existing.get("checkcomponents_sent"):
|
||||
pay_record["checkcomponents_sent"] = True
|
||||
schedule.save_pay_record(week_key, pay_record)
|
||||
|
||||
prev_sunday = prev_monday + timedelta(days=6)
|
||||
|
|
@ -349,14 +354,6 @@ def handler(event, context):
|
|||
week_key,
|
||||
)
|
||||
|
||||
try:
|
||||
_send_checkcomponents(pay_record)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"checkcomponents send failed week=%s",
|
||||
week_key,
|
||||
)
|
||||
|
||||
# --- Two-week schedule (always starts on Monday of this week) ---
|
||||
this_monday = now - timedelta(days=now.weekday())
|
||||
week_start = this_monday.strftime("%Y-%m-%d")
|
||||
|
|
@ -417,6 +414,29 @@ def handler(event, context):
|
|||
exc_info=True,
|
||||
)
|
||||
raise
|
||||
|
||||
# Enqueue after the schedule post is persisted so an EventBridge retry
|
||||
# caused by save_schedule_post cannot send the same week twice. A sent
|
||||
# flag on the pay record covers a later timeout or a forced re-run.
|
||||
if pay_record["breakdown"] and not pay_record.get("checkcomponents_sent"):
|
||||
sent = False
|
||||
try:
|
||||
sent = _send_checkcomponents(pay_record)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"checkcomponents send failed week=%s",
|
||||
week_key,
|
||||
)
|
||||
if sent:
|
||||
try:
|
||||
pay_record["checkcomponents_sent"] = True
|
||||
schedule.save_pay_record(week_key, pay_record)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"checkcomponents sent flag failed week=%s",
|
||||
week_key,
|
||||
)
|
||||
|
||||
return {
|
||||
"posted": True,
|
||||
"channel": channel_id,
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ def test_calculates_and_dms_pay(weeklypost_app, schedule, seed, slack, env, sqs)
|
|||
assert body["windowEnd"] == "2026-06-07"
|
||||
assert "payPeriodId" not in body
|
||||
assert body["lines"] == [{"extension": "114", "amount": "50.00"}]
|
||||
assert schedule.get_pay_record("2026-06-01")["checkcomponents_sent"] is True
|
||||
|
||||
|
||||
@freeze_time(MON_0800)
|
||||
|
|
@ -200,6 +201,40 @@ def test_empty_queue_url_skips_checkcomponents(
|
|||
|
||||
assert result["posted"] is True
|
||||
sqs.send_message.assert_not_called()
|
||||
assert "checkcomponents_sent" not in (schedule.get_pay_record("2026-06-01") or {})
|
||||
|
||||
|
||||
@freeze_time(MON_0800)
|
||||
def test_save_failure_does_not_enqueue_checkcomponents(
|
||||
weeklypost_app, schedule, seed, slack, env, monkeypatch, sqs
|
||||
):
|
||||
seed.config(shift_rate="50")
|
||||
seed.weekly("Monday", "114", "Alice")
|
||||
monkeypatch.setattr(
|
||||
weeklypost_app.ShiftSchedule,
|
||||
"save_schedule_post",
|
||||
MagicMock(side_effect=Exception("dynamo down")),
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="dynamo down"):
|
||||
weeklypost_app.handler({"force": True}, None)
|
||||
|
||||
sqs.send_message.assert_not_called()
|
||||
assert "checkcomponents_sent" not in (schedule.get_pay_record("2026-06-01") or {})
|
||||
|
||||
|
||||
@freeze_time(MON_0800)
|
||||
def test_retry_does_not_resend_checkcomponents(
|
||||
weeklypost_app, schedule, seed, slack, env, sqs
|
||||
):
|
||||
seed.config(shift_rate="50")
|
||||
seed.weekly("Monday", "114", "Alice")
|
||||
|
||||
weeklypost_app.handler({"force": True}, None)
|
||||
weeklypost_app.handler({"force": True}, None)
|
||||
|
||||
sqs.send_message.assert_called_once()
|
||||
assert schedule.get_pay_record("2026-06-01")["checkcomponents_sent"] is True
|
||||
|
||||
|
||||
def test_payload_skips_fallback_and_zero(weeklypost_app):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue