mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 07:53:11 +00:00
Support per-person shift pay rates
Each roster entry can now have its own shift_rate, falling back to the global CONFIG rate when not set. Pay breakdowns show individual rates per shift and per-person totals.
This commit is contained in:
parent
57c411824c
commit
1399be7e74
4 changed files with 23 additions and 13 deletions
|
|
@ -171,7 +171,7 @@ def create_app(bot_token: str, signing_secret: str) -> App:
|
|||
prev_sunday = prev_monday + timedelta(days=6)
|
||||
week_label = f"{prev_monday.strftime('%b %-d')} to {prev_sunday.strftime('%b %-d')}"
|
||||
blocks = build_pay_summary_blocks(
|
||||
week_label, pay_record["breakdown"], pay_record["totals"], float(pay_record["rate"])
|
||||
week_label, pay_record["breakdown"], pay_record["totals"]
|
||||
)
|
||||
respond(blocks=blocks)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -120,22 +120,22 @@ def build_help_blocks() -> list[dict]:
|
|||
return [{"type": "section", "text": {"type": "mrkdwn", "text": text}}]
|
||||
|
||||
|
||||
def build_pay_summary_blocks(week_label: str, pay_breakdown: list[dict], total_by_person: dict[str, dict], rate: float) -> list[dict]:
|
||||
def build_pay_summary_blocks(week_label: str, pay_breakdown: list[dict], total_by_person: dict[str, dict]) -> list[dict]:
|
||||
"""Build Block Kit blocks for the weekly pay summary.
|
||||
|
||||
Args:
|
||||
week_label: e.g. "Mar 30 to Apr 5"
|
||||
pay_breakdown: list of {date, day, name, extension, rate} per shift
|
||||
total_by_person: {name: {shifts: int, total: float, extension: str}}
|
||||
rate: flat rate per shift
|
||||
total_by_person: {name: {shifts: int, total: float, extension: str, rate: float}}
|
||||
"""
|
||||
lines = [f"_Rate: ${rate:.2f} per shift_\n"]
|
||||
lines = []
|
||||
for entry in pay_breakdown:
|
||||
lines.append(f"{entry['day']} {entry['date_label']} — {entry['name']} (Ext {entry['extension']})")
|
||||
lines.append(f"{entry['day']} {entry['date_label']} — {entry['name']} (Ext {entry['extension']}) — ${entry['rate']:.2f}")
|
||||
|
||||
lines.append("\n*Totals:*")
|
||||
for name, info in sorted(total_by_person.items()):
|
||||
lines.append(f"• {name} — {info['shifts']} shift{'s' if info['shifts'] != 1 else ''} — *${info['total']:.2f}*")
|
||||
rate_note = f" @ ${info['rate']:.2f}/shift" if info.get("rate") else ""
|
||||
lines.append(f"• {name} — {info['shifts']} shift{'s' if info['shifts'] != 1 else ''}{rate_note} — *${info['total']:.2f}*")
|
||||
|
||||
blocks = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -127,7 +127,15 @@ class ShiftSchedule:
|
|||
resp = self.table.get_item(Key={"PK": "CONFIG", "SK": "CONFIG"})
|
||||
return resp.get("Item", {})
|
||||
|
||||
def get_shift_rate(self) -> float:
|
||||
"""Get the flat per-shift pay rate from config."""
|
||||
def get_shift_rate(self, extension: str | None = None) -> float:
|
||||
"""Get the per-shift pay rate.
|
||||
|
||||
Checks the roster entry for a person-specific rate first,
|
||||
then falls back to the global config rate.
|
||||
"""
|
||||
if extension:
|
||||
employee = self.get_employee_by_extension(extension)
|
||||
if employee and employee.get("shift_rate"):
|
||||
return float(employee["shift_rate"])
|
||||
config = self.get_config()
|
||||
return float(config.get("shift_rate", 0))
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ DAY_ORDER = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
|
|||
|
||||
def _calculate_weekly_pay(schedule: ShiftSchedule, week_start: datetime) -> dict:
|
||||
"""Calculate pay for a Mon–Sun week. Returns pay record dict."""
|
||||
rate = schedule.get_shift_rate()
|
||||
default_rate = schedule.get_shift_rate()
|
||||
breakdown = []
|
||||
totals = {}
|
||||
|
||||
|
|
@ -35,6 +35,8 @@ def _calculate_weekly_pay(schedule: ShiftSchedule, week_start: datetime) -> dict
|
|||
if source == "available":
|
||||
continue
|
||||
|
||||
rate = schedule.get_shift_rate(ext)
|
||||
|
||||
breakdown.append({
|
||||
"date": date_str,
|
||||
"date_label": date.strftime("%b %-d"),
|
||||
|
|
@ -45,13 +47,13 @@ def _calculate_weekly_pay(schedule: ShiftSchedule, week_start: datetime) -> dict
|
|||
})
|
||||
|
||||
if name not in totals:
|
||||
totals[name] = {"shifts": 0, "total": 0.0, "extension": ext}
|
||||
totals[name] = {"shifts": 0, "total": 0.0, "extension": ext, "rate": rate}
|
||||
totals[name]["shifts"] += 1
|
||||
totals[name]["total"] += rate
|
||||
|
||||
return {
|
||||
"week_start": week_start.strftime("%Y-%m-%d"),
|
||||
"rate": str(rate),
|
||||
"default_rate": str(default_rate),
|
||||
"breakdown": breakdown,
|
||||
"totals": totals,
|
||||
}
|
||||
|
|
@ -88,7 +90,7 @@ def handler(event, context):
|
|||
prev_sunday = prev_monday + timedelta(days=6)
|
||||
week_label = f"{prev_monday.strftime('%b %-d')} to {prev_sunday.strftime('%b %-d')}"
|
||||
pay_blocks = build_pay_summary_blocks(
|
||||
week_label, pay_record["breakdown"], pay_record["totals"], float(pay_record["rate"])
|
||||
week_label, pay_record["breakdown"], pay_record["totals"]
|
||||
)
|
||||
slack.chat_postMessage(
|
||||
channel=channel_id,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue