From fd89426ed976660d8323f535f3a9ab9ae195d7a6 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 21 Feb 2026 08:53:17 +0000 Subject: [PATCH] Group cart tickets by event with +/- quantity buttons - Cart page groups tickets by entry+type instead of listing individually - Each group shows event name, type, date, qty with +/- buttons, line total - New POST /cart/ticket-quantity/ route for adjusting from cart page - Summary includes ticket quantities in Items count Co-Authored-By: Claude Opus 4.6 --- app.py | 2 + bp/cart/global_routes.py | 22 ++++++ bp/cart/page_routes.py | 4 ++ bp/cart/services/ticket_groups.py | 43 ++++++++++++ templates/_types/cart/_cart.html | 113 ++++++++++++++++++++++++------ 5 files changed, 161 insertions(+), 23 deletions(-) create mode 100644 bp/cart/services/ticket_groups.py diff --git a/app.py b/app.py index eeb6e7e..c714bdd 100644 --- a/app.py +++ b/app.py @@ -28,6 +28,7 @@ from bp.cart.services.page_cart import ( get_calendar_entries_for_page, get_tickets_for_page, ) +from bp.cart.services.ticket_groups import group_tickets async def _load_cart(): @@ -78,6 +79,7 @@ async def cart_context() -> dict: ctx["calendar_cart_entries"] = all_cal ctx["ticket_cart_entries"] = all_tickets + ctx["ticket_groups"] = group_tickets(ctx.get("ticket_cart_entries", [])) ctx["total"] = total ctx["calendar_total"] = calendar_total ctx["ticket_total"] = ticket_total diff --git a/bp/cart/global_routes.py b/bp/cart/global_routes.py index fccbe6a..a05fe46 100644 --- a/bp/cart/global_routes.py +++ b/bp/cart/global_routes.py @@ -81,6 +81,28 @@ def register(url_prefix: str) -> Blueprint: resp.headers["HX-Refresh"] = "true" return resp + @bp.post("/ticket-quantity/") + async def update_ticket_quantity(): + """Adjust reserved ticket count (+/- pattern, like products).""" + ident = current_cart_identity() + form = await request.form + entry_id = int(form.get("entry_id", 0)) + count = max(int(form.get("count", 0)), 0) + tt_raw = (form.get("ticket_type_id") or "").strip() + ticket_type_id = int(tt_raw) if tt_raw else None + + await services.calendar.adjust_ticket_quantity( + g.s, entry_id, count, + user_id=ident["user_id"], + session_id=ident["session_id"], + ticket_type_id=ticket_type_id, + ) + await g.s.flush() + + resp = await make_response("", 200) + resp.headers["HX-Refresh"] = "true" + return resp + @bp.post("/delete//") async def delete_item(product_id: int): ident = current_cart_identity() diff --git a/bp/cart/page_routes.py b/bp/cart/page_routes.py index 69e20b0..88c507f 100644 --- a/bp/cart/page_routes.py +++ b/bp/cart/page_routes.py @@ -14,6 +14,7 @@ from .services import ( ticket_total, ) from .services.page_cart import get_cart_for_page, get_calendar_entries_for_page, get_tickets_for_page +from .services.ticket_groups import group_tickets from .services.checkout import ( create_order_from_cart, build_sumup_description, @@ -33,12 +34,15 @@ def register(url_prefix: str) -> Blueprint: cal_entries = await get_calendar_entries_for_page(g.s, post.id) page_tickets = await get_tickets_for_page(g.s, post.id) + ticket_groups = group_tickets(page_tickets) + tpl_ctx = dict( page_post=post, page_config=getattr(g, "page_config", None), cart=cart, calendar_cart_entries=cal_entries, ticket_cart_entries=page_tickets, + ticket_groups=ticket_groups, total=total, calendar_total=calendar_total, ticket_total=ticket_total, diff --git a/bp/cart/services/ticket_groups.py b/bp/cart/services/ticket_groups.py new file mode 100644 index 0000000..cd5d910 --- /dev/null +++ b/bp/cart/services/ticket_groups.py @@ -0,0 +1,43 @@ +"""Group individual TicketDTOs by (entry_id, ticket_type_id) for cart display.""" +from __future__ import annotations + +from collections import OrderedDict + + +def group_tickets(tickets) -> list[dict]: + """ + Group a flat list of TicketDTOs into aggregate rows. + + Returns list of dicts: + { + "entry_id": int, + "entry_name": str, + "entry_start_at": datetime, + "entry_end_at": datetime | None, + "ticket_type_id": int | None, + "ticket_type_name": str | None, + "price": Decimal | None, + "quantity": int, + "line_total": float, + } + """ + groups: OrderedDict[tuple, dict] = OrderedDict() + + for tk in tickets: + key = (tk.entry_id, getattr(tk, "ticket_type_id", None)) + if key not in groups: + groups[key] = { + "entry_id": tk.entry_id, + "entry_name": tk.entry_name, + "entry_start_at": tk.entry_start_at, + "entry_end_at": tk.entry_end_at, + "ticket_type_id": getattr(tk, "ticket_type_id", None), + "ticket_type_name": tk.ticket_type_name, + "price": tk.price, + "quantity": 0, + "line_total": 0, + } + groups[key]["quantity"] += 1 + groups[key]["line_total"] += float(tk.price or 0) + + return list(groups.values()) diff --git a/templates/_types/cart/_cart.html b/templates/_types/cart/_cart.html index d564c0d..f21e098 100644 --- a/templates/_types/cart/_cart.html +++ b/templates/_types/cart/_cart.html @@ -60,37 +60,102 @@ {% endif %} - {% if ticket_cart_entries %} + {% if ticket_groups is defined and ticket_groups %}

+ Event tickets

-
    - {% for tk in ticket_cart_entries %} -
  • -
    -
    - {{ tk.entry_name }} -
    - {% if tk.ticket_type_name %} -
    - {{ tk.ticket_type_name }} +
    + {% for tg in ticket_groups %} +
    +
    +
    +
    +

    + {{ tg.entry_name }} +

    + {% if tg.ticket_type_name %} +

    + {{ tg.ticket_type_name }} +

    + {% endif %} +

    + {{ tg.entry_start_at.strftime('%-d %b %Y, %H:%M') }} + {% if tg.entry_end_at %} + – {{ tg.entry_end_at.strftime('%-d %b %Y, %H:%M') }} + {% endif %} +

    +
    +
    +

    + £{{ "%.2f"|format(tg.price or 0) }} +

    +
    +
    + +
    +
    + Quantity + {% set qty_url = url_for('cart_global.update_ticket_quantity') %} + +
    + + + {% if tg.ticket_type_id %} + + {% endif %} + + +
    + + + {{ tg.quantity }} + + +
    + + + {% if tg.ticket_type_id %} + + {% endif %} + + +
    +
    + +
    +

    + Line total: + £{{ "%.2f"|format(tg.line_total) }} +

    - {% endif %} -
    - {{ tk.entry_start_at.strftime('%-d %b %Y, %H:%M') }} - {% if tk.entry_end_at %} - – {{ tk.entry_end_at.strftime('%-d %b %Y, %H:%M') }} - {% endif %}
    -
    - £{{ "%.2f"|format(tk.price or 0) }} -
    -
  • + {% endfor %} -
+
{% endif %} @@ -114,7 +179,9 @@
Items
- {{ cart | sum(attribute="quantity") }} + {% set product_qty = cart | sum(attribute="quantity") %} + {% set ticket_qty = ticket_cart_entries | length if ticket_cart_entries else 0 %} + {{ product_qty + ticket_qty }}