afterhours-shift-manager/tests/weekly_post/test_pay_math.py
Adam Moussa dbef3bda52
Some checks failed
Deploy / deploy (push) Has been cancelled
Deploy / release (push) Has been cancelled
chore(pay): remove weekly-post payroll email code, env, and failure alarm (#249)
PR #248 disabled the SES send by blanking PAYROLL_RECIPIENTS and dropping the
ses:SendEmail grant. This removes the now-dead path: _send_pay_email and
_build_pay_email_html, the SES_SENDER and PAYROLL_RECIPIENTS env, and the
PayrollEmailFailure metric filter and alarm that only fired on that path.
Slack schedule post, pay-summary DM, and checkcomponents enqueue unchanged.
2026-09-10 19:58:59 +00:00

209 lines
9 KiB
Python

"""Tests for weekly-post pay calculation helpers."""
from datetime import datetime
from decimal import Decimal
# 2026-06-01 is a Monday — required so DAY_ORDER lines up with real weekdays.
WEEK_START = datetime(2026, 6, 1)
class TestAddShiftToPay:
def test_assigned_shift_is_added(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.weekly("Monday", "114", "Alice")
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 1), "Monday"
)
assert len(breakdown) == 1
assert breakdown[0]["name"] == "Alice"
assert breakdown[0]["rate"] == Decimal("50")
assert totals["Alice"]["shifts"] == 1
assert totals["Alice"]["total"] == Decimal("50")
def test_available_shift_is_skipped(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 1), "Monday"
)
assert breakdown == [] and totals == {}
def test_weekend_day_label(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.weekly("Saturday", "200", "Alice", shift_type="day")
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 6), "Saturday", "day"
)
assert breakdown[0]["day"] == "Sat (Day)"
class TestCalculateWeeklyPay:
def test_single_weekday_shift(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.weekly("Monday", "114", "Alice")
record = weeklypost_app._calculate_weekly_pay(schedule, WEEK_START)
assert record["week_start"] == "2026-06-01"
assert len(record["breakdown"]) == 1
assert record["totals"]["Alice"]["total"] == Decimal("50")
def test_per_employee_rate_applied(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.roster("114", "Alice", shift_rate="75")
seed.weekly("Monday", "114", "Alice")
record = weeklypost_app._calculate_weekly_pay(schedule, WEEK_START)
assert record["totals"]["Alice"]["total"] == Decimal("75.0")
def test_weekend_counts_day_and_night(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.weekly("Saturday", "200", "Alice", shift_type="day")
seed.weekly("Saturday", "200", "Alice", shift_type="night")
record = weeklypost_app._calculate_weekly_pay(schedule, WEEK_START)
# Alice worked both Saturday shifts → 2 shifts, $100.
assert record["totals"]["Alice"]["shifts"] == 2
assert record["totals"]["Alice"]["total"] == Decimal("100")
def test_empty_week_has_no_breakdown(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
record = weeklypost_app._calculate_weekly_pay(schedule, WEEK_START)
assert record["breakdown"] == [] and record["totals"] == {}
class TestHolidayPay:
def test_single_holiday_assignee(self, weeklypost_app, schedule, seed):
# Wed 2026-06-03 holiday; one assignee at base $50 * 1.5 = $75.
seed.config(shift_rate="50")
seed.holiday(
"2026-06-03",
slots=2,
label="Founders Day",
assignees={"114": {"name": "Alice", "claimed_at": "x"}},
)
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 3), "Wednesday", "day"
)
assert len(breakdown) == 1
line = breakdown[0]
assert line["name"] == "Alice"
assert line["is_holiday"] is True
assert line["holiday_label"] == "Founders Day"
assert line["multiplier"] == Decimal("1.5")
assert line["base_rate"] == Decimal("50")
# rate is the effective (multiplied) pay for Slack-block compatibility.
assert line["rate"] == Decimal("75.0")
assert line["amount"] == Decimal("75.0")
assert isinstance(line["multiplier"], Decimal)
assert isinstance(line["amount"], Decimal)
assert totals["Alice"]["shifts"] == 1
assert totals["Alice"]["holiday_shifts"] == 1
assert totals["Alice"]["total"] == Decimal("75.0")
def test_two_assignees_same_holiday(self, weeklypost_app, schedule, seed):
# Two slots filled on the same holiday; each paid base * 1.5.
seed.config(shift_rate="40")
seed.roster("114", "Alice", shift_rate="40")
seed.roster("200", "Bob", shift_rate="60")
seed.holiday(
"2026-06-03",
slots=2,
label="Founders Day",
assignees={
"114": {"name": "Alice", "claimed_at": "x"},
"200": {"name": "Bob", "claimed_at": "y"},
},
)
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 3), "Wednesday", "day"
)
assert len(breakdown) == 2
assert all(line["is_holiday"] for line in breakdown)
by_name = {line["name"]: line for line in breakdown}
assert by_name["Alice"]["amount"] == Decimal("60.0") # 40 * 1.5
assert by_name["Bob"]["amount"] == Decimal("90.0") # 60 * 1.5
assert totals["Alice"]["total"] == Decimal("60.0")
assert totals["Bob"]["total"] == Decimal("90.0")
assert totals["Alice"]["holiday_shifts"] == 1
assert totals["Bob"]["holiday_shifts"] == 1
def test_custom_multiplier(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.holiday(
"2026-06-03",
slots=1,
label="Double Day",
assignees={"114": {"name": "Alice", "claimed_at": "x"}},
multiplier=Decimal("2"),
)
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 3), "Wednesday", "day"
)
assert breakdown[0]["multiplier"] == Decimal("2")
assert breakdown[0]["amount"] == Decimal("100")
assert totals["Alice"]["total"] == Decimal("100")
def test_empty_holiday_adds_nothing(self, weeklypost_app, schedule, seed):
seed.config(shift_rate="50")
seed.holiday("2026-06-03", slots=2, label="Empty", assignees={})
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 3), "Wednesday", "day"
)
assert breakdown == [] and totals == {}
def test_mixed_week(self, weeklypost_app, schedule, seed):
# A week with a weekday night shift, a weekend day+night, and a holiday.
seed.config(shift_rate="50")
# Regular Monday night for Alice.
seed.weekly("Monday", "114", "Alice")
# Holiday on Wednesday (day shift) with Alice + Bob.
seed.roster("200", "Bob", shift_rate="50")
seed.holiday(
"2026-06-03",
slots=2,
label="Founders Day",
assignees={
"114": {"name": "Alice", "claimed_at": "x"},
"200": {"name": "Bob", "claimed_at": "y"},
},
)
# Saturday day + night for Bob (regular weekend).
seed.weekly("Saturday", "200", "Bob", shift_type="day")
seed.weekly("Saturday", "200", "Bob", shift_type="night")
record = weeklypost_app._calculate_weekly_pay(schedule, WEEK_START)
# Alice: Mon night $50 + Wed holiday $75 = $125 over 2 shifts.
assert record["totals"]["Alice"]["shifts"] == 2
assert record["totals"]["Alice"]["total"] == Decimal("125.0")
assert record["totals"]["Alice"]["holiday_shifts"] == 1
# Bob: Wed holiday $75 + Sat day $50 + Sat night $50 = $175 over 3 shifts.
assert record["totals"]["Bob"]["shifts"] == 3
assert record["totals"]["Bob"]["total"] == Decimal("175.0")
assert record["totals"]["Bob"]["holiday_shifts"] == 1
# Exactly two holiday breakdown lines (Alice + Bob on Wed).
holiday_lines = [b for b in record["breakdown"] if b.get("is_holiday")]
assert len(holiday_lines) == 2
assert {line["name"] for line in holiday_lines} == {"Alice", "Bob"}
def test_night_on_holiday_date_is_regular(self, weeklypost_app, schedule, seed):
# A holiday only applies to the day shift; night falls through to weekly.
seed.config(shift_rate="50")
seed.weekly("Wednesday", "114", "Alice")
seed.holiday(
"2026-06-03",
slots=1,
label="Founders Day",
assignees={"200": {"name": "Bob", "claimed_at": "y"}},
)
breakdown, totals = [], {}
weeklypost_app._add_shift_to_pay(
schedule, breakdown, totals, datetime(2026, 6, 3), "Wednesday", "night"
)
assert len(breakdown) == 1
assert breakdown[0]["name"] == "Alice"
assert breakdown[0].get("is_holiday") is None
assert breakdown[0]["amount"] == Decimal("50")