mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 10:13:11 +00:00
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.
209 lines
9 KiB
Python
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")
|