- Users can delete their own bookings (confirmation page) - Crew page tree (/suff/staff/): book/pay/delete for any user, register new users, set/reset PINs - Anonymous walk-in user "anonym": bookings auto-create matching cash payment so balance stays at 0 - Self-signup: unknown name creates account (PIN required); known name without PIN and no activity allows claim (PIN required); known name without PIN but with activity blocks and points to bar crew - Crew-only PIN set/reset; no random PINs, PINs never displayed - Cyan .staff-target highlight on all crew pages - Updated suff.md with current feature state and open ideas Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5.4 KiB
Suff – drink booking tool
Self-service drink tab for festival attendees. Lives at /suff/. Plain Django, no JS.
Auth
User.pin(hashed CharField, 3 digits), separate frompassword. Strong password stays for/admin/.PinBackendauthenticates byusername+pin.ModelBackendfirst inAUTHENTICATION_BACKENDSso admin still needs strong password.- PIN reset is crew-only. No self-reset, no random PINs, PINs never displayed.
Name flow
Username = slugify(input). POST name:
- not found →
createmode → mandatory 3-digit PIN → create user → login - found, has PIN →
loginmode → enter PIN - found, no PIN, no activity (no Consumption + no UserPayment) →
claimmode → set PIN → login - found, no PIN, has activity →
no_pin.html("ask someone at the bar")
Booking
/suff/me/shows: greeting, total/paid/open balance, drink grid, day-grouped history.+1POST createsConsumption(amount=1, day=current_weekday, for_free=False).- Trash icon per row →
confirm_delete.html→ POST deletes own consumption (booking phase only). - History grouped by festival day, newest-first per day.
Payments
/suff/pay/— user enters amount + method (cash/paypal/bank/other) + optional note. CreatesUserPayment. Pre-fills with currentopen_balance.- Method choices:
UserPayment.Method. - Open balance = sum(Consumption.price where !for_free) − sum(UserPayment.amount).
Crew (is_staff)
Separate page tree under /suff/staff/:
/suff/staff/— alphabetical user list, anonymous gast on top, "Neuen Benutzer anlegen" link./suff/staff/new/— register user. Name required, PIN optional 3 digits./suff/staff/u/<name>/— book/pay/delete for that user. Mirrorsme.html./suff/staff/u/<name>/pin/— overwrite PIN (3 digits required, no clear)./suff/staff/u/<name>/pay/— record payment for that user./suff/staff/u/<name>/book/<id>/delete/— delete consumption (and matching auto-payment if anon).
Target username highlighted via .staff-target (cyan pill) on every crew page.
Anonymous walk-ins
- Seeded user
anonym(migration 0009). No PIN, never logs in. - Crew books for anonymous via staff_user page → drink booking auto-creates matching
UserPayment(method=cash, note="Auto: <drink>")so balance always 0. - Deleting an anonymous consumption removes one matching auto-payment.
- Anonymous has no pay page (404). PayPal walk-ins → register a real user instead.
Time gating (Berlin tz)
- Phases:
before/booking/closed. - Test window: 2026-05-15 → 2026-05-31. Original festival: 2026-06-11 → 2026-06-14.
closedshows static page; outside booking, all action endpoints redirect or 404.settings.PRODUCTION=Falseforcesbooking.
Dashboard
/suff/dashboard/(staff only). Donations vs. expenses with progress bar, drink inventory rows, refinance %, per-user open balances, top spender, top drink, busiest day, top drinker per day.
Drink categories
Drink.category: beer / alc_free_beer / radler / alc_free_radler / soft / water.- Buttons gradient-colored per category. Sorted by category in grid.
Files
gaehsnitz/auth_backends.py—PinBackendgaehsnitz/suff.py— all suff views + phase + crew helpersgaehsnitz/suff_urls.py— routesgaehsnitz/admin.py—SetPinForm+set_pin_viewgaehsnitz/templates/suff/{base,name,pin,no_pin,me,pay,dashboard,closed,confirm_delete,staff_index,staff_user,staff_pay,staff_register,staff_pin_reset,staff_confirm_delete}.htmlgaehsnitz/static/suff/{style.css,favicon.svg}gaehsnitz/migrations/0009_anonymous_user.py— seedsanonym
Frontend
Mobile-first dark theme. #161616 bg, #EE9933/#FFCC77 amber, #885522 brown borders, #66ddee cyan for crew target. Drink buttons gradient-colored per category. Toast banner for booking confirmation. :active scale feedback. SVG favicon.
Open ideas / next session
Entry fee handling
Festival entry is flexible (10–30 €, sometimes paid as overpayment on drink tab). Options brainstormed:
- A. Implicit overpayment — pre-fill pay form with
open_balance + 20, treat anything above drinks as entry/donation. Zero schema change. - B. Quick-pick suggestions — pay page offers buttons:
Nur Getränke (X €),+10,+20,+30, plus free-form. Recommended. - C. Explicit split — separate entry field/checkbox. More UI, more accurate stats (
entry = paid − consumed), more friction.
Lean B. Stats can later derive entry as paid − consumed_drinks_price per user.
Pay-on-the-spot / quick-pay-cash
Single button on me (and crew_user) page: "Offenen Betrag bar bezahlen" → creates UserPayment(method=cash, amount=open_balance). Lets bar crew clear tab in one tap when guest pays cash directly. (Skipped for now, keep in mind.)
Round to nearest 10 €
When pre-filling pay amount or showing quick-pick buttons, optionally round up to nearest multiple of 10 €. E.g. drinks 26.50 € → suggest 30, 40, 50. Easier cash handling, implicit donation/entry.
Prepay vs. pay-at-end
Currently a single open balance. Could surface "Prepay 50 €" as a flow vs. "Pay at the end" — same data model, different framing. Maybe a "Vorkasse" preset on pay page.
Misc
- PWA manifest for add-to-homescreen
- Drink icons/emoji per type
- Style phase pages (
before/closed) - Per-user QR for fast crew lookup at the bar