d612acd715
- 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>
106 lines
5.3 KiB
Markdown
106 lines
5.3 KiB
Markdown
# 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.
|
||
- 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.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
|
||
|
||
### 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
|