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>
This commit is contained in:
@@ -1,64 +1,112 @@
|
||||
# Suff – drink booking tool
|
||||
|
||||
Self-service drink tab for festival attendees. Lives at `/suff/`. Plain Django, no JS, no CSS yet.
|
||||
Self-service drink tab for festival attendees. Lives at `/suff/`. Plain Django, no JS.
|
||||
|
||||
## Auth
|
||||
|
||||
- `User.pin` field (hashed CharField) stores 3-digit PIN, separate from `password`. Lets staff keep a strong password for `/admin/` and use the same username on `/suff/` with just a PIN.
|
||||
- `PinBackend` (`gaehsnitz/auth_backends.py`) authenticates by `username` + `pin` via `user.check_pin()`. Default `ModelBackend` stays first in `AUTHENTICATION_BACKENDS` so `/admin/` keeps requiring the strong password.
|
||||
- Staff PINs cannot be self-set on `/suff/`. If a name matches an existing user with no PIN, the user lands on `suff/no_pin.html` explaining that an admin must set the PIN via the admin panel — otherwise anyone could claim a staff name and lock out the real owner.
|
||||
- Admin convenience: User change page shows PIN status ("gesetzt"/"nicht gesetzt") + "PIN setzen" link → custom admin view `<id>/pin/` with a 3-digit form, calls `user.set_pin()`.
|
||||
- `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)` (e.g. "Flo Hä!" → "flo-ha"). Slug shown back so user can memorize it.
|
||||
- POST name → check existence:
|
||||
- not found → set new PIN → create user → login
|
||||
- found, has PIN → enter PIN → login
|
||||
- found, no PIN → `no_pin.html` (ask admin)
|
||||
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 (slug), running paid total, full consumption history with timestamps, drink buttons.
|
||||
- Each drink = `+1` POST form. Server creates `Consumption(amount=1, day=current_weekday, for_free=False, created_at=auto)`.
|
||||
- No undo, no delete, no edit. No special bartender role.
|
||||
- History sorted newest-first, `created_at` shown as `Do 18:42` etc.
|
||||
- `/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` / `readonly` / `closed`.
|
||||
- Booking allowed Thu 2026-06-11 00:00 → Sun 2026-06-14 23:59.
|
||||
- Read-only until Sun 2026-06-21 23:59.
|
||||
- After: every `/suff/` URL returns 404.
|
||||
- Local dev: `settings.PRODUCTION=False` forces `booking` phase always.
|
||||
- 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` — views + phase logic
|
||||
- `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}.html`
|
||||
- `gaehsnitz/templates/admin/gaehsnitz/user/set_pin.html`
|
||||
- `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/0003_consumption_created_at_user_pin.py`
|
||||
- Edits: `gaehsnitzproject/settings.py`, `gaehsnitzproject/urls.py`, `gaehsnitz/models.py`
|
||||
- `gaehsnitz/migrations/0009_anonymous_user.py` — seeds `anonym`
|
||||
|
||||
## Frontend
|
||||
|
||||
Mobile-first styled. Dark theme matching GOA palette (`#161616` bg, `#EE9933`/`#FFCC77` amber accents, `#885522` brown borders). Standalone microsite — no nav to main GOA page.
|
||||
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.
|
||||
|
||||
- Landing/login: GOA subhead + big "Suff" wordmark, `name` and `pin` forms with stacked label/input, large tap targets
|
||||
- `me` page: 2-col drink button grid (4:3 aspect), stacked +1 / name / price; bordered total box; day-grouped history with zebra rows; emoji empty-state
|
||||
- Booking confirmation: amber toast, 5s display, then 800ms collapse animation (pure CSS, no JS)
|
||||
- `:active` scale(0.96) feedback on buttons + link-buttons
|
||||
- `no_pin.html` link-buttons styled (primary + secondary)
|
||||
- SVG favicon (🍺)
|
||||
## Open ideas / next session
|
||||
|
||||
## Further ideas
|
||||
### 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
|
||||
|
||||
- Color-code drink buttons (per-drink accent border or bg — Bier amber, Wasser blue, etc.) for fast visual recognition in dim light
|
||||
- Drink icons/emoji per type
|
||||
- Style phase pages (`before` / `closed` if non-404)
|
||||
- PWA manifest for add-to-homescreen
|
||||
- Donation/free-drink flow if needed (currently admin-only via `for_free`)
|
||||
- Drink icons/emoji per type
|
||||
- Style phase pages (`before` / `closed`)
|
||||
- Per-user QR for fast crew lookup at the bar
|
||||
|
||||
Reference in New Issue
Block a user