Files
gaehsnitz/suff.md
T
flo d612acd715 Add settled balance panel, cash booking mode, and Kasse dashboard line
- Balance panel turns green with "Bezahlt ✓" + breakdown when open_balance <= 0, on all four tab pages (me, pay, staff_user, staff_pay)
- booking_mode radio on me.html and staff_user.html: normal / for_free / cash_paid; cash_paid auto-creates matching UserPayment(method=cash)
- Dashboard finance section shows "Kasse (bar)" sum of all cash payments for cross-checking
- staff_pay prefill lower-capped at 0; "Zahlung eintragen" always visible on staff_user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:02:14 +02:00

5.3 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.
  • Single form wraps two radio buttons (booking_mode) + all drink buttons (name="drink_id").
  • booking_mode values: normal (default, no radio selected), for_free, cash_paid.
  • +1 POST creates Consumption(amount=1, day=current_weekday, for_free=...).
  • cash_paid booking auto-creates matching UserPayment(method=cash) — same as anonymous walk-ins.
  • 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).
  • Balance panel shows settled state (green "Bezahlt ✓" + breakdown) when open_balance <= 0; amber with "Offen X €" otherwise. Applied on /suff/me/, /suff/pay/, /suff/staff/u/<name>/, /suff/staff/u/<name>/pay/.

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. "Zahlung eintragen" link always visible.
  • /suff/staff/u/<name>/pin/ — overwrite PIN (3 digits required, no clear).
  • /suff/staff/u/<name>/pay/ — record payment for that user. Amount pre-fills with max(open_balance, 0).
  • /suff/staff/u/<name>/book/<id>/delete/ — delete consumption (and matching auto-payment if anon).

Staff booking form has same booking_mode radios (normal / for_free / cash_paid) as user view.

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.
  • Finance section includes "Kasse (bar)" — sum of all UserPayment(method=cash) — for cross-checking real cash in the box.

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

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.)

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