# 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 activity** → `no_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//` — book/pay/delete for that user. Mirrors `me.html`. - `/suff/staff/u//pin/` — overwrite PIN (3 digits required, no clear). - `/suff/staff/u//pay/` — record payment for that user. - `/suff/staff/u//book//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: ")` 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.py` — `PinBackend` - `gaehsnitz/suff.py` — all suff views + phase + crew helpers - `gaehsnitz/suff_urls.py` — routes - `gaehsnitz/admin.py` — `SetPinForm` + `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 (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