mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-09-30 08:53:13 +00:00
Some checks are pending
Deploy / deploy (push) Waiting to run
Generate a per-person weekly summary PDF at Thursday close and store it
alongside the CSV reports, plus a client-side admin download that rolls
orders up into item -> total quantity for bulk ordering.
- shared/pdf.py: build_weekly_summary_pdf() via fpdf2 (pure-Python,
ARM64-safe; first non-boto3 layer dep). Per-person employee -> item ->
quantity, no pricing.
- aggregate_orders: write reports/{week}/weekly-summary-{week}.pdf
(application/pdf) and stamp weekly_summary_pdf_s3_key on the SUMMARY.
No new IAM (existing S3CrudPolicy). No email/Slack delivery.
- generate_form.py: "Download order list" admin button aggregates the
loaded week's orders into an item->qty CSV (no per-employee breakdown,
no prices) via a Blob download. Works for open weeks too.
- Tests: tests/test_pdf.py; aggregate happy-path now asserts 3 S3
uploads + the pdf key.
- README updated.
160 lines
8 KiB
Markdown
160 lines
8 KiB
Markdown
# 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
|
||
```
|