# meal-order-manager Automates weekly meal ordering from [Redefine Meals](https://www.redefinemeals.com) for Sea Haven Industries employees. Scrapes the menu, generates an order form, collects individual orders, and produces payroll deduction reports plus a per-person weekly summary PDF. ## Architecture ``` Monday 7:30am ET Employees (Mon–Thu) Thursday 6pm ET ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ GitHub Actions │ │ orders.seahaven │ │ EventBridge │ │ - Scrape menu │────S3 upload───▶│ ind.com │ │ - Close form │ │ - Generate form │ + DynamoDB │ (CloudFront+S3) │──POST───┐ │ - Aggregate │ │ - Slack notify │ └──────────────────┘ │ │ - Slack summary │ └─────────────────┘ ▼ └──────────────────┘ ┌──────────┐ Monday 7am ET │ API GW + │ ┌──────────────────┐ │ Lambda │ │ EventBridge │ │ submit │ │ - Email payroll │ └────┬─────┘ │ deductions │ ▼ └──────────────────┘ ┌──────────┐ │ DynamoDB │ Thu 10am: Slack DM │ orders │ reminders to employees └──────────┘ who haven't ordered ``` ## Weekly Flow | When | What | How | |------|------|-----| | Monday 6:55am ET | Sync employee roster from Slack channel membership | EventBridge → Lambda → DynamoDB | | Monday 7am ET | Email previous week's payroll deductions to `payroll@` | EventBridge → Lambda → SES | | Monday 7:30am ET | Scrape menu, generate form, upload to S3, post link to Slack | GitHub Actions cron | | Mon–Thu | Employees visit `orders.seahaven.com` and submit orders | S3 static form → API Gateway → Lambda → DynamoDB | | Thursday 10am ET | DM employees who haven't ordered yet | EventBridge → Lambda → Slack DM | | Thursday 6pm ET | Close form, aggregate orders, write CSV reports + weekly summary PDF, post Redefine order summary to Slack | EventBridge → Lambda chain | ### Reports (written to `meal-order-manager-reports-*` at Thursday close) | Key | Contents | |-----|----------| | `reports/{week}/order-summary.csv` | Meal-level aggregate (meal, qty, unit price, line total) for the Redefine order | | `reports/{week}/payroll-deductions.csv` | Per-employee payroll deduction totals | | `reports/{week}/weekly-summary-{week}.pdf` | Per-person summary (employee → item → quantity, **no pricing**); stored only, not emailed | ## AWS Resources Stack name: `meal-order-manager` (us-east-1) - **S3** — `meal-order-manager-form-*` (static form hosting), `meal-order-manager-reports-*` (CSV reports + weekly summary PDF) - **CloudFront** — HTTPS distribution with custom domain `orders.seahaven.com` - **DynamoDB** — `meal-order-manager-orders` (orders, menu, roster, config) - **API Gateway** — HttpApi for order submission and admin operations - **Lambda** — 6 functions: submit-order, close-form, aggregate-orders, slack-notifier, sync-roster, email-report - **EventBridge** — scheduled rules (dual EST/EDT) for close, reminders, payroll email - **Secrets Manager** — Slack bot token, form API key - **SES** — payroll deduction emails ## Authentication Google Identity Services (OAuth) with tokeninfo endpoint verification. Accepts both `seahavenind.com` and `seahaven.com` Google Workspace domains. ## Admin Panel Admins (configured in DynamoDB `CONFIG/SETTINGS` → `admin_emails` list) get an "Admin" button after Google sign-in. The panel provides: - View all orders by week with totals - Edit order quantities, add new menu items, remove items - Delete orders entirely - **Download order list** — a CSV rollup of item → total quantity across all employees (no per-employee breakdown, no prices) to drive the bulk Redefine order. Generated client-side from the loaded week, so it works for open weeks too. All admin operations enforce server-side price recalculation from the menu. **API routes** (all require Google auth + admin email): | Method | Path | Description | |--------|------|-------------| | GET | `/api/admin/orders` | List weeks with order counts | | GET | `/api/admin/orders?week=YYYY-WNN` | Get all orders for a week | | PUT | `/api/admin/orders` | Update an order (recalculates prices) | | DELETE | `/api/admin/orders?week=...&email=...` | Delete an order | ## Setup ### Local development ```bash python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt playwright install chromium ``` ### Deploy to AWS ```bash cp samconfig.toml.example samconfig.toml # Edit samconfig.toml with your certificate ARN, etc. sam build sam deploy ``` ### Post-deploy 1. Create the Slack bot token secret: `aws secretsmanager create-secret --name meal-order-manager/slack-bot-token --secret-string "xoxb-..."` 2. Create the form API key secret: `aws secretsmanager create-secret --name meal-order-manager/form-api-key --secret-string "$(openssl rand -hex 32)"` 3. Update the Slack channel SSM parameter: `aws ssm put-parameter --name /meal-order-manager/slack-channel-id --value "C0XXXXXXX" --overwrite` 4. Verify SES sender identity for `adam@seahavenind.com` 5. Set up DNS: CNAME `orders.seahaven.com` → CloudFront distribution domain 6. Roster syncs automatically from Slack channel members (runs Monday 6:55am ET), or seed manually: `python3 scripts/seed_roster.py` ## Local Workflow (no AWS) The scraper, form generator, Flask server, and aggregator still work locally: ```bash python3 src/scraper/scrape_menu.py # scrape menu python3 src/server/generate_form.py # generate form (local mode) python3 src/server/app.py # serve on localhost:5050 python3 src/aggregator/aggregate.py # generate CSV reports ``` ## Configuration `config.json` (local dev): - `menu_url` — Redefine Meals menu URL - `order_deadline` — displayed on the form - `roster` — employee list (name, email, slack_user_id) - `output_dir` / `orders_dir` — local output paths ## Project Structure ``` meal-order-manager/ ├── .github/workflows/ │ ├── weekly-menu.yml # Monday cron: scrape + publish + notify │ ├── ci.yml # PR checks │ └── deploy.yml # Push to main: sam deploy ├── src/ │ ├── scraper/ # Playwright menu scraper │ ├── server/ # Form generator + local Flask server │ ├── aggregator/ # Order aggregation + CSV reports │ └── shared/shared/ # Lambda layer (db, secrets, slack, pdf helpers) ├── functions/ # Lambda handlers │ ├── submit_order/ │ ├── close_form/ │ ├── aggregate_orders/ │ ├── slack_notifier/ │ ├── sync_roster/ │ └── email_report/ ├── scripts/ # CI/CD helper scripts │ ├── upload_menu.py │ ├── notify_slack.py │ └── seed_roster.py ├── template.yaml # SAM template ├── samconfig.toml.example ├── slack-app-manifest.yml # Slack app manifest (paste into api.slack.com) └── config.json ```