From 1aa28b38fdddf863fc400686e6ab6cf2c71d2a03 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 22 May 2026 12:24:21 -0400 Subject: [PATCH] Add admin form bypass and send order summary to admin DMs (#15) Admins (by email in settings) can now view and submit orders even after the form closes. The orders-aggregated Slack summary is sent as a DM to each admin instead of posting to the channel. --- functions/slack_notifier/handler.py | 21 ++++- functions/submit_order/handler.py | 6 +- src/server/generate_form.py | 40 +++++++--- tests/test_submit_order.py | 114 ++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 13 deletions(-) diff --git a/functions/slack_notifier/handler.py b/functions/slack_notifier/handler.py index d9387dd..f4324dd 100644 --- a/functions/slack_notifier/handler.py +++ b/functions/slack_notifier/handler.py @@ -4,7 +4,7 @@ from datetime import datetime from decimal import Decimal from zoneinfo import ZoneInfo -from shared.db import current_week, get_orders, get_roster, get_summary +from shared.db import current_week, get_orders, get_roster, get_settings, get_summary from shared.slack import post_channel_message, send_dm @@ -121,8 +121,23 @@ def handle_orders_aggregated(event): }, ] - post_channel_message(text, blocks) - return {"status": "notified", "event": "orders_aggregated", "week": week} + settings = get_settings() + admin_emails = [e.lower() for e in settings.get("admin_emails", [])] + roster = get_roster() + dm_count = 0 + for email in admin_emails: + employee = next((e for e in roster if e["email"].lower() == email), None) + if not employee or not employee.get("slack_user_id"): + continue + send_dm(employee["slack_user_id"], text, blocks) + dm_count += 1 + + return { + "status": "notified", + "event": "orders_aggregated", + "week": week, + "admin_dm_count": dm_count, + } def handle_order_confirmed(event): diff --git a/functions/submit_order/handler.py b/functions/submit_order/handler.py index a1178cc..c90fcae 100644 --- a/functions/submit_order/handler.py +++ b/functions/submit_order/handler.py @@ -429,7 +429,11 @@ def handle_submit(event): week = current_week() status = get_form_status(week) - if status == "closed": + is_admin_user = False + if _google_auth_configured(): + admin_emails = {e.lower() for e in get_settings().get("admin_emails", [])} + is_admin_user = email.lower() in admin_emails + if status == "closed" and not is_admin_user: return response(410, {"error": "Orders are closed for this week"}) if status == "not_found": return response(404, {"error": "No menu available for this week"}) diff --git a/src/server/generate_form.py b/src/server/generate_form.py index baafdad..fed517f 100644 --- a/src/server/generate_form.py +++ b/src/server/generate_form.py @@ -151,7 +151,7 @@ function signOut() { if use_google_auth: submit_order_js = """ async function submitOrder() { - if (formClosed) { alert('Orders are closed.'); return; } + if (formClosed && !isAdmin) { alert('Orders are closed.'); return; } if (!googleCredential || !googleUser) { alert('Please sign in with Google first.'); return; } const items = Object.entries(quantities).map(([i, qty]) => ({ @@ -194,7 +194,7 @@ async function submitOrder() { else: submit_order_js = """ async function submitOrder() { - if (formClosed) { alert('Orders are closed.'); return; } + if (formClosed && !isAdmin) { alert('Orders are closed.'); return; } const name = document.getElementById('emp-name').value.trim(); const email = document.getElementById('emp-email').value.trim(); if (!name) { alert('Please enter your name.'); return; } @@ -555,9 +555,9 @@ function renderMeals() {{
${{tagsHtml}}
- + - +
`; list.appendChild(card); @@ -622,6 +622,23 @@ function updateTotal() {{ } ''' } +function adminBypassClosed() {{ + document.getElementById('closed-overlay').style.display = 'none'; + document.querySelectorAll('.qty-control button').forEach(b => b.disabled = false); + var submitBtn = document.getElementById('submit-btn'); + if (submitBtn) submitBtn.disabled = Object.keys(quantities).length === 0; + var existing = document.getElementById('admin-closed-banner'); + if (!existing) {{ + var banner = document.createElement('div'); + banner.id = 'admin-closed-banner'; + banner.className = 'closed-banner'; + banner.textContent = 'Form is closed — admin override active'; + var deadline = document.querySelector('.deadline'); + if (deadline) deadline.parentNode.insertBefore(banner, deadline.nextSibling); + }} + renderMeals(); +}} + function checkAdmin() {{ if (!ADMIN_URL) return; const headers = {{}}; @@ -636,6 +653,7 @@ function checkAdmin() {{ adminWeeks = data.weeks; var ab = document.getElementById('admin-btn'); if (ab) ab.style.display = 'inline-block'; + if (formClosed) adminBypassClosed(); }} }}) .catch(() => {{}}); @@ -844,11 +862,15 @@ async function checkFormStatus() {{ const data = await res.json(); if (data.status === 'closed') {{ formClosed = true; - document.getElementById('closed-overlay').style.display = 'flex'; - document.querySelectorAll('.qty-control button').forEach(b => b.disabled = true); - const submitBtn = document.getElementById('submit-btn'); - if (submitBtn) submitBtn.disabled = true; - startCountdown(data); + if (isAdmin) {{ + adminBypassClosed(); + }} else {{ + document.getElementById('closed-overlay').style.display = 'flex'; + document.querySelectorAll('.qty-control button').forEach(b => b.disabled = true); + const submitBtn = document.getElementById('submit-btn'); + if (submitBtn) submitBtn.disabled = true; + startCountdown(data); + }} }} }} catch (e) {{}} }} diff --git a/tests/test_submit_order.py b/tests/test_submit_order.py index 535fa5d..9caeaf1 100644 --- a/tests/test_submit_order.py +++ b/tests/test_submit_order.py @@ -1297,6 +1297,120 @@ def test_submit_form_closed( mock_put.assert_not_called() +@patch("submit_order_handler._lambda") +@patch("submit_order_handler.put_order") +@patch("submit_order_handler.get_form_status", return_value="closed") +@patch("submit_order_handler.current_week", return_value="2026-W20") +@patch( + "submit_order_handler.get_settings", + return_value={ + "bulk_discount_percent": 0, + "company_subsidy_percent": 0, + "admin_emails": ["adam@seahavenind.com"], + }, +) +@patch("submit_order_handler.get_secret", return_value=TEST_API_KEY) +@patch( + "submit_order_handler._get_google_client_id", return_value=VALID_GOOGLE_CLIENT_ID +) +@patch("submit_order_handler.urllib.request.urlopen") +@patch("submit_order_handler._google_auth_configured", return_value=True) +@patch("submit_order_handler.get_menu") +def test_admin_can_submit_when_form_closed( + mock_get_menu, + mock_gac, + mock_urlopen, + mock_gcid, + mock_secret, + mock_settings, + mock_week, + mock_status, + mock_put, + mock_lam, +): + """Admin user can submit even when form is closed.""" + from submit_order_handler import lambda_handler + + mock_resp = MagicMock() + mock_resp.read.return_value = json.dumps( + { + "aud": VALID_GOOGLE_CLIENT_ID, + "hd": "seahavenind.com", + "name": "Adam Moussa", + "email": "adam@seahavenind.com", + } + ).encode() + mock_resp.__enter__ = MagicMock(return_value=mock_resp) + mock_resp.__exit__ = MagicMock(return_value=False) + mock_urlopen.return_value = mock_resp + + items = _make_items([(10.00, 1)]) + mock_get_menu.return_value = _menu_doc_from_retail_pairs([(10.00, 1)]) + event = _submit_event(items, extra_body={"google_id_token": "admin-token"}) + result = lambda_handler(event, None) + status, body = _parse_response(result) + + assert status == 200, f"Admin should bypass closed form, got {status}: {body}" + mock_put.assert_called_once() + + +@patch("submit_order_handler._lambda") +@patch("submit_order_handler.put_order") +@patch("submit_order_handler.get_form_status", return_value="closed") +@patch("submit_order_handler.current_week", return_value="2026-W20") +@patch( + "submit_order_handler.get_settings", + return_value={ + "bulk_discount_percent": 0, + "company_subsidy_percent": 0, + "admin_emails": ["adam@seahavenind.com"], + }, +) +@patch("submit_order_handler.get_secret", return_value=TEST_API_KEY) +@patch( + "submit_order_handler._get_google_client_id", return_value=VALID_GOOGLE_CLIENT_ID +) +@patch("submit_order_handler.urllib.request.urlopen") +@patch("submit_order_handler._google_auth_configured", return_value=True) +@patch("submit_order_handler.get_menu") +def test_non_admin_blocked_when_form_closed( + mock_get_menu, + mock_gac, + mock_urlopen, + mock_gcid, + mock_secret, + mock_settings, + mock_week, + mock_status, + mock_put, + mock_lam, +): + """Non-admin user still blocked when form is closed.""" + from submit_order_handler import lambda_handler + + mock_resp = MagicMock() + mock_resp.read.return_value = json.dumps( + { + "aud": VALID_GOOGLE_CLIENT_ID, + "hd": "seahavenind.com", + "name": "Regular Employee", + "email": "employee@seahavenind.com", + } + ).encode() + mock_resp.__enter__ = MagicMock(return_value=mock_resp) + mock_resp.__exit__ = MagicMock(return_value=False) + mock_urlopen.return_value = mock_resp + + items = _make_items([(10.00, 1)]) + mock_get_menu.return_value = _menu_doc_from_retail_pairs([(10.00, 1)]) + event = _submit_event(items, extra_body={"google_id_token": "user-token"}) + result = lambda_handler(event, None) + status, body = _parse_response(result) + + assert status == 410, f"Non-admin should be blocked, got {status}: {body}" + mock_put.assert_not_called() + + @patch("submit_order_handler._lambda") @patch("submit_order_handler.put_order") @patch("submit_order_handler.get_form_status", return_value="not_found")