Replace ticket qty input with +/- buttons, show sold/basket counts
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 57s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 57s
- Entry page shows tickets sold count, remaining, and "in basket" count - Replace numeric input + Buy button with add-to-basket / +/- controls - New POST /tickets/adjust/ route creates/cancels tickets to target count - Keep buy form active after adding (no confirmation replacement) - New service functions: get_sold_ticket_count, get_user_reserved_count, cancel_latest_reserved_ticket Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ Routes:
|
||||
GET /tickets/ — My tickets list
|
||||
GET /tickets/<code>/ — Ticket detail with QR code
|
||||
POST /tickets/buy/ — Purchase tickets for an entry
|
||||
POST /tickets/adjust/ — Adjust ticket quantity (+/-)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -26,6 +27,9 @@ from .services.tickets import (
|
||||
get_user_tickets,
|
||||
get_available_ticket_count,
|
||||
get_tickets_for_entry,
|
||||
get_sold_ticket_count,
|
||||
get_user_reserved_count,
|
||||
cancel_latest_reserved_ticket,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -178,4 +182,119 @@ def register() -> Blueprint:
|
||||
)
|
||||
return await make_response(html, 200)
|
||||
|
||||
@bp.post("/adjust/")
|
||||
@clear_cache(tag="calendars", tag_scope="all")
|
||||
async def adjust_quantity():
|
||||
"""
|
||||
Adjust ticket quantity for a calendar entry (+/- pattern).
|
||||
Creates or cancels tickets to reach the target count.
|
||||
|
||||
Form fields:
|
||||
entry_id — the calendar entry ID
|
||||
ticket_type_id — (optional) specific ticket type
|
||||
count — target quantity of reserved tickets
|
||||
"""
|
||||
form = await request.form
|
||||
|
||||
entry_id_raw = form.get("entry_id", "").strip()
|
||||
if not entry_id_raw:
|
||||
return await make_response("Entry ID required", 400)
|
||||
try:
|
||||
entry_id = int(entry_id_raw)
|
||||
except ValueError:
|
||||
return await make_response("Invalid entry ID", 400)
|
||||
|
||||
# Load entry
|
||||
entry = await g.s.scalar(
|
||||
select(CalendarEntry)
|
||||
.where(
|
||||
CalendarEntry.id == entry_id,
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
)
|
||||
.options(selectinload(CalendarEntry.ticket_types))
|
||||
)
|
||||
if not entry:
|
||||
return await make_response("Entry not found", 404)
|
||||
if entry.ticket_price is None:
|
||||
return await make_response("Tickets not available for this entry", 400)
|
||||
|
||||
# Ticket type (optional)
|
||||
ticket_type_id = None
|
||||
tt_raw = form.get("ticket_type_id", "").strip()
|
||||
if tt_raw:
|
||||
try:
|
||||
ticket_type_id = int(tt_raw)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
target = max(int(form.get("count", 0)), 0)
|
||||
ident = current_cart_identity()
|
||||
|
||||
current = await get_user_reserved_count(
|
||||
g.s, entry_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
ticket_type_id=ticket_type_id,
|
||||
)
|
||||
|
||||
if target > current:
|
||||
# Need to add tickets
|
||||
to_add = target - current
|
||||
available = await get_available_ticket_count(g.s, entry_id)
|
||||
if available is not None and to_add > available:
|
||||
return await make_response(
|
||||
f"Only {available} ticket(s) remaining", 400
|
||||
)
|
||||
for _ in range(to_add):
|
||||
await create_ticket(
|
||||
g.s,
|
||||
entry_id=entry_id,
|
||||
ticket_type_id=ticket_type_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
state="reserved",
|
||||
)
|
||||
elif target < current:
|
||||
# Need to remove tickets
|
||||
to_remove = current - target
|
||||
for _ in range(to_remove):
|
||||
await cancel_latest_reserved_ticket(
|
||||
g.s, entry_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
ticket_type_id=ticket_type_id,
|
||||
)
|
||||
|
||||
# Build context for re-rendering the buy form
|
||||
ticket_remaining = await get_available_ticket_count(g.s, entry_id)
|
||||
ticket_sold_count = await get_sold_ticket_count(g.s, entry_id)
|
||||
user_ticket_count = await get_user_reserved_count(
|
||||
g.s, entry_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
)
|
||||
|
||||
# Per-type counts for multi-type entries
|
||||
user_ticket_counts_by_type = {}
|
||||
if entry.ticket_types:
|
||||
for tt in entry.ticket_types:
|
||||
if tt.deleted_at is None:
|
||||
user_ticket_counts_by_type[tt.id] = await get_user_reserved_count(
|
||||
g.s, entry_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
ticket_type_id=tt.id,
|
||||
)
|
||||
|
||||
html = await render_template(
|
||||
"_types/tickets/_adjust_response.html",
|
||||
entry=entry,
|
||||
ticket_remaining=ticket_remaining,
|
||||
ticket_sold_count=ticket_sold_count,
|
||||
user_ticket_count=user_ticket_count,
|
||||
user_ticket_counts_by_type=user_ticket_counts_by_type,
|
||||
)
|
||||
|
||||
return await make_response(html, 200)
|
||||
|
||||
return bp
|
||||
|
||||
Reference in New Issue
Block a user