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.
This commit is contained in:
Adam Moussa 2026-05-22 12:24:21 -04:00 • committed by GitHub
parent 5db95ce9d1
commit 1aa28b38fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 168 additions and 13 deletions

View file

@ -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):

View file

@ -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"})

View file

@ -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() {{
<div class="meal-tags">${{tagsHtml}}</div>
</div>
<div class="qty-control">
<button onclick="changeQty(${{i}}, -1)" ${{formClosed ? 'disabled' : ''}}>&minus;</button>
<button onclick="changeQty(${{i}}, -1)" ${{formClosed && !isAdmin ? 'disabled' : ''}}>&minus;</button>
<input class="qty" type="text" value="${{qty}}" readonly>
<button onclick="changeQty(${{i}}, 1)" ${{formClosed ? 'disabled' : ''}}>+</button>
<button onclick="changeQty(${{i}}, 1)" ${{formClosed && !isAdmin ? 'disabled' : ''}}>+</button>
</div>
`;
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) {{}}
}}

View file

@ -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")