mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-10-04 17:12:05 +00:00
Email weekly pay summary to payroll via SES on Mondays
Sends an HTML pay summary email to configured payroll recipients alongside the existing Slack post. Uses SES with noreply@seahaven.com as sender. Recipients configured via PAYROLL_RECIPIENTS env var (set to adam@seahaven.com for testing, switch to payroll@seahaven.com for production). Closes #3
This commit is contained in:
parent
093bb43e71
commit
9deb8bf251
2 changed files with 72 additions and 2 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
"""Lambda handler — posts the weekly on-call schedule and previous week's pay summary
|
"""Lambda handler — posts the weekly on-call schedule and previous week's pay summary
|
||||||
to Slack every Monday at 7am ET."""
|
to Slack every Monday at 7am ET, and emails the pay summary to payroll."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
@ -74,6 +74,66 @@ def _calculate_weekly_pay(schedule: ShiftSchedule, week_start: datetime) -> dict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _build_pay_email_html(week_label: str, pay_record: dict) -> str:
|
||||||
|
"""Build an HTML email body for the weekly pay summary."""
|
||||||
|
rows = ""
|
||||||
|
for entry in pay_record["breakdown"]:
|
||||||
|
rows += (
|
||||||
|
f"<tr><td>{entry['day']}</td><td>{entry['date_label']}</td>"
|
||||||
|
f"<td>{entry['name']}</td><td>Ext {entry['extension']}</td>"
|
||||||
|
f"<td>${entry['rate']:.2f}</td></tr>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
totals_rows = ""
|
||||||
|
for name, info in sorted(pay_record["totals"].items()):
|
||||||
|
totals_rows += (
|
||||||
|
f"<tr><td>{name}</td><td>{info['shifts']}</td>"
|
||||||
|
f"<td>${info.get('rate', 0):.2f}</td><td><strong>${info['total']:.2f}</strong></td></tr>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"""<html>
|
||||||
|
<body style="font-family: Arial, sans-serif; color: #333;">
|
||||||
|
<h2>After-Hours Pay Summary — {week_label}</h2>
|
||||||
|
|
||||||
|
<h3>Shift Breakdown</h3>
|
||||||
|
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse;">
|
||||||
|
<tr style="background: #f0f0f0;"><th>Day</th><th>Date</th><th>Name</th><th>Extension</th><th>Rate</th></tr>
|
||||||
|
{rows}</table>
|
||||||
|
|
||||||
|
<h3>Totals by Person</h3>
|
||||||
|
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse;">
|
||||||
|
<tr style="background: #f0f0f0;"><th>Name</th><th>Shifts</th><th>Rate</th><th>Total</th></tr>
|
||||||
|
{totals_rows}</table>
|
||||||
|
|
||||||
|
<p style="color: #888; font-size: 12px;">This is an automated report from the After-Hours Shift Manager.</p>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
def _send_pay_email(week_label: str, pay_record: dict) -> None:
|
||||||
|
"""Send the weekly pay summary email via SES."""
|
||||||
|
sender = os.environ.get("SES_SENDER", "noreply@seahaven.com")
|
||||||
|
recipients = os.environ.get("PAYROLL_RECIPIENTS", "").split(",")
|
||||||
|
recipients = [r.strip() for r in recipients if r.strip()]
|
||||||
|
|
||||||
|
if not recipients:
|
||||||
|
logger.warning("No PAYROLL_RECIPIENTS configured — skipping email")
|
||||||
|
return
|
||||||
|
|
||||||
|
ses = boto3.client("ses")
|
||||||
|
html_body = _build_pay_email_html(week_label, pay_record)
|
||||||
|
|
||||||
|
ses.send_email(
|
||||||
|
Source=sender,
|
||||||
|
Destination={"ToAddresses": recipients},
|
||||||
|
Message={
|
||||||
|
"Subject": {"Data": f"After-Hours Pay Summary — {week_label}"},
|
||||||
|
"Body": {"Html": {"Data": html_body}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
logger.info("Sent pay email to %s", recipients)
|
||||||
|
|
||||||
|
|
||||||
def handler(event, context):
|
def handler(event, context):
|
||||||
now = datetime.now(EASTERN)
|
now = datetime.now(EASTERN)
|
||||||
|
|
||||||
|
|
@ -114,6 +174,9 @@ def handler(event, context):
|
||||||
)
|
)
|
||||||
logger.info("Posted pay summary for week of %s", week_key)
|
logger.info("Posted pay summary for week of %s", week_key)
|
||||||
|
|
||||||
|
# Email pay summary to payroll
|
||||||
|
_send_pay_email(week_label, pay_record)
|
||||||
|
|
||||||
# --- Two-week schedule ---
|
# --- Two-week schedule ---
|
||||||
blocks = build_week_schedule(schedule, start_date=now)
|
blocks = build_week_schedule(schedule, start_date=now)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,11 @@ Resources:
|
||||||
SHIFT_TABLE: !Ref ShiftTable
|
SHIFT_TABLE: !Ref ShiftTable
|
||||||
SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token
|
SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token
|
||||||
SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id
|
SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id
|
||||||
|
SES_SENDER: noreply@seahaven.com
|
||||||
|
PAYROLL_RECIPIENTS: adam@seahaven.com
|
||||||
TZ: !Ref Timezone
|
TZ: !Ref Timezone
|
||||||
Policies:
|
Policies:
|
||||||
- DynamoDBReadPolicy:
|
- DynamoDBCrudPolicy:
|
||||||
TableName: !Ref ShiftTable
|
TableName: !Ref ShiftTable
|
||||||
- Statement:
|
- Statement:
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
|
|
@ -106,6 +108,11 @@ Resources:
|
||||||
Condition:
|
Condition:
|
||||||
StringEquals:
|
StringEquals:
|
||||||
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
|
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- ses:SendEmail
|
||||||
|
Resource:
|
||||||
|
- !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/seahaven.com"
|
||||||
Events:
|
Events:
|
||||||
# EST: 7am ET = 12:00 UTC (Nov-Mar)
|
# EST: 7am ET = 12:00 UTC (Nov-Mar)
|
||||||
WeeklyPostEST:
|
WeeklyPostEST:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue