diff --git a/functions/email_report/handler.py b/functions/email_report/handler.py deleted file mode 100644 index 5fd7f7e..0000000 --- a/functions/email_report/handler.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -from email.mime.application import MIMEApplication -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText - -import boto3 - -from shared.db import get_summary, previous_week - -_ses = boto3.client("ses") -_s3 = boto3.client("s3") - - -def lambda_handler(event, context): - week = event.get("week", previous_week()) - - summary = get_summary(week) - if not summary: - return {"status": "no_summary", "week": week} - - payroll_key = summary.get("payroll_csv_s3_key") - if not payroll_key: - return {"status": "no_payroll_csv", "week": week} - - bucket = os.environ["REPORTS_BUCKET"] - csv_obj = _s3.get_object(Bucket=bucket, Key=payroll_key) - csv_content = csv_obj["Body"].read() - - total_employees = int(summary.get("total_employees", 0)) - grand_total = float(summary.get("grand_total", 0)) - - msg = MIMEMultipart("mixed") - msg["Subject"] = f"Meal Order Payroll Deductions — {week}" - msg["From"] = os.environ["SENDER_EMAIL"] - msg["To"] = os.environ["PAYROLL_EMAIL"] - - body = MIMEText( - f"Attached is the meal order payroll deduction report for {week}.\n\n" - f"Employees: {total_employees}\n" - f"Total deductions: ${grand_total:.2f}\n\n" - f"Please apply these deductions in the next pay period.\n", - "plain", - ) - msg.attach(body) - - attachment = MIMEApplication(csv_content) - attachment.add_header( - "Content-Disposition", "attachment", filename=f"payroll-deductions-{week}.csv" - ) - msg.attach(attachment) - - _ses.send_raw_email( - Source=os.environ["SENDER_EMAIL"], - Destinations=[os.environ["PAYROLL_EMAIL"]], - RawMessage={"Data": msg.as_string()}, - ) - - return {"status": "sent", "week": week, "to": os.environ["PAYROLL_EMAIL"]} diff --git a/functions/email_report/requirements.txt b/functions/email_report/requirements.txt deleted file mode 100644 index 348b557..0000000 --- a/functions/email_report/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -boto3==1.43.78 diff --git a/samconfig.toml.example b/samconfig.toml.example index 328f48c..75d7b60 100644 --- a/samconfig.toml.example +++ b/samconfig.toml.example @@ -7,4 +7,4 @@ s3_prefix = "meal-order-manager" region = "us-east-1" confirm_changeset = true capabilities = "CAPABILITY_IAM" -parameter_overrides = "CustomDomain=orders.seahaven.com CertificateArn=arn:aws:acm:us-east-1:328440206208:certificate/CHANGE-ME PayrollEmail=payroll@seahavenind.com SenderEmail=adam@seahavenind.com" +parameter_overrides = "CustomDomain=orders.seahaven.com CertificateArn=arn:aws:acm:us-east-1:328440206208:certificate/CHANGE-ME" diff --git a/template.yaml b/template.yaml index b2f5737..19ae498 100644 --- a/template.yaml +++ b/template.yaml @@ -13,14 +13,6 @@ Parameters: Type: String Default: '' Description: ACM certificate ARN for the custom domain (us-east-1) - PayrollEmail: - Type: String - Default: payroll@seahavenind.com - Description: Email address for payroll deduction reports - SenderEmail: - Type: String - Default: adam@seahavenind.com - Description: SES verified sender email for payroll reports # Shared CloudFront WAF WebACL ARN (audit M-17), published to SSM by # seahaven-account-baseline. Resolved at deploy time. WebAclArn: @@ -543,42 +535,6 @@ Resources: Description: 'Sync roster Monday 6:55am EDT (10:55 UTC) — before menu publish' Enabled: true - EmailReportFunction: - Type: AWS::Serverless::Function - Properties: - FunctionName: meal-order-manager-email-report - Handler: handler.lambda_handler - CodeUri: functions/email_report/ - MemorySize: 128 - Timeout: 30 - Environment: - Variables: - PAYROLL_EMAIL: !Ref PayrollEmail - SENDER_EMAIL: !Ref SenderEmail - Policies: - - DynamoDBReadPolicy: - TableName: !Ref OrdersTable - - S3ReadPolicy: - BucketName: !Ref ReportsBucket - - Statement: - - Effect: Allow - Action: - - ses:SendRawEmail - Resource: '*' - Events: - PayrollEmailEST: - Type: Schedule - Properties: - Schedule: cron(0 12 ? * MON *) - Description: 'Email payroll deductions Monday 7am EST (12:00 UTC)' - Enabled: true - PayrollEmailEDT: - Type: Schedule - Properties: - Schedule: cron(0 11 ? * MON *) - Description: 'Email payroll deductions Monday 7am EDT (11:00 UTC)' - Enabled: true - # ─── CloudWatch Log Groups (60-day retention) ────────────────── SubmitOrderLogGroup: @@ -605,12 +561,6 @@ Resources: LogGroupName: !Sub '/aws/lambda/${SlackNotifierFunction}' RetentionInDays: 60 - EmailReportLogGroup: - Type: AWS::Logs::LogGroup - Properties: - LogGroupName: !Sub '/aws/lambda/${EmailReportFunction}' - RetentionInDays: 60 - SyncRosterLogGroup: Type: AWS::Logs::LogGroup Properties: @@ -744,25 +694,6 @@ Resources: AlarmActions: - arn:aws:sns:us-east-1:328440206208:site-alerts - EmailReportErrorsAlarm: - Type: AWS::CloudWatch::Alarm - Properties: - AlarmName: meal-order-manager-email-report-errors - AlarmDescription: email-report Lambda reported one or more errors in 5 minutes. - Namespace: AWS/Lambda - MetricName: Errors - Dimensions: - - Name: FunctionName - Value: !Ref EmailReportFunction - Statistic: Sum - Period: 300 - EvaluationPeriods: 1 - Threshold: 0 - ComparisonOperator: GreaterThanThreshold - TreatMissingData: notBreaching - AlarmActions: - - arn:aws:sns:us-east-1:328440206208:site-alerts - # Lambda Throttles (Sum, 5min, any throttle breaches) SubmitOrderThrottlesAlarm: Type: AWS::CloudWatch::Alarm @@ -878,25 +809,6 @@ Resources: AlarmActions: - arn:aws:sns:us-east-1:328440206208:site-alerts - EmailReportThrottlesAlarm: - Type: AWS::CloudWatch::Alarm - Properties: - AlarmName: meal-order-manager-email-report-throttles - AlarmDescription: email-report Lambda was throttled in the last 5 minutes. - Namespace: AWS/Lambda - MetricName: Throttles - Dimensions: - - Name: FunctionName - Value: !Ref EmailReportFunction - Statistic: Sum - Period: 300 - EvaluationPeriods: 1 - Threshold: 0 - ComparisonOperator: GreaterThanThreshold - TreatMissingData: notBreaching - AlarmActions: - - arn:aws:sns:us-east-1:328440206208:site-alerts - # Lambda Duration p99 (~80% of timeout) # Synchronous (API-fronted) functions: eval 3 / datapoints 3. SubmitOrderDurationAlarm: @@ -1020,26 +932,6 @@ Resources: AlarmActions: - arn:aws:sns:us-east-1:328440206208:site-alerts - EmailReportDurationAlarm: - Type: AWS::CloudWatch::Alarm - Properties: - AlarmName: meal-order-manager-email-report-duration - AlarmDescription: email-report p99 duration exceeded 24000ms (80% of 30s timeout). - Namespace: AWS/Lambda - MetricName: Duration - Dimensions: - - Name: FunctionName - Value: !Ref EmailReportFunction - ExtendedStatistic: p99 - Period: 300 - EvaluationPeriods: 1 - DatapointsToAlarm: 1 - Threshold: 24000 - ComparisonOperator: GreaterThanThreshold - TreatMissingData: notBreaching - AlarmActions: - - arn:aws:sns:us-east-1:328440206208:site-alerts - # DynamoDB orders table. # NOTE: DynamoDB does NOT publish ThrottledRequests or SystemErrors at the # TableName-only dimension (verified via cloudwatch list-metrics on @@ -1202,6 +1094,3 @@ Outputs: SyncRosterFunctionArn: Description: Sync Roster Lambda ARN Value: !GetAtt SyncRosterFunction.Arn - EmailReportFunctionArn: - Description: Email Report Lambda ARN - Value: !GetAtt EmailReportFunction.Arn diff --git a/tests/test_terraform_email_report.py b/tests/test_terraform_email_report.py index 0365f52..98fe3bd 100644 --- a/tests/test_terraform_email_report.py +++ b/tests/test_terraform_email_report.py @@ -1,14 +1,19 @@ -"""email_report is removed from the live Terraform config (PLAT-135).""" +"""email_report is removed from Terraform, SAM, and the functions tree (PLAT-135).""" from pathlib import Path -TERRAFORM = Path(__file__).resolve().parents[1] / "terraform" +ROOT = Path(__file__).resolve().parents[1] +TERRAFORM = ROOT / "terraform" def _read(name: str) -> str: return (TERRAFORM / name).read_text() +def test_email_report_handler_removed(): + assert not (ROOT / "functions" / "email_report").exists() + + def test_email_report_not_in_function_packages(): locals_tf = _read("locals.tf") assert '"email_report"' not in locals_tf @@ -36,3 +41,12 @@ def test_email_report_lambda_and_role_removed(): assert "email_report" not in outputs assert "payroll_email" not in variables assert "sender_email" not in variables + + +def test_email_report_removed_from_sam_template(): + template = (ROOT / "template.yaml").read_text() + assert "EmailReportFunction" not in template + assert "functions/email_report/" not in template + assert "PayrollEmail" not in template + assert "SenderEmail" not in template + assert "ses:SendRawEmail" not in template