Files
gaehsnitz/suff.md
T
flo 50fc32c577 Add booking deletion, crew views, anonymous walk-in, and account claim
- 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>
2026-05-26 18:05:20 +02:00

5.4 KiB
Raw Blame History

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 from password. Strong password stays for /admin/.
  • PinBackend authenticates by username + pin. ModelBackend first in AUTHENTICATION_BACKENDS so 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 → create mode → mandatory 3-digit PIN → create user → login
  • found, has PIN → login mode → enter PIN
  • found, no PIN, no activity (no Consumption + no UserPayment) → claim mode → set PIN → login
  • found, no PIN, has activityno_pin.html ("ask someone at the bar")

Booking

  • /suff/me/ shows: greeting, total/paid/open balance, drink grid, day-grouped history.
  • +1 POST creates Consumption(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. Creates UserPayment. Pre-fills with current open_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. Mirrors me.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.
  • closed shows static page; outside booking, all action endpoints redirect or 404.
  • settings.PRODUCTION=False forces booking.

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.pyPinBackend
  • gaehsnitz/suff.py — all suff views + phase + crew helpers
  • gaehsnitz/suff_urls.py — routes
  • gaehsnitz/admin.pySetPinForm + set_pin_view
  • gaehsnitz/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}.html
  • gaehsnitz/static/suff/{style.css,favicon.svg}
  • gaehsnitz/migrations/0009_anonymous_user.py — seeds anonym

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 (1030 €, 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