""" Page-scoped cart queries. Groups cart items and calendar entries by their owning page (Post), determined via CartItem.market_place_container_id (where container_type == "page"). """ from __future__ import annotations from collections import defaultdict from types import SimpleNamespace from sqlalchemy import select from shared.models.market import CartItem from shared.infrastructure.data_client import fetch_data from shared.contracts.dtos import CalendarEntryDTO, TicketDTO, PostDTO, dto_from_dict from .identity import current_cart_identity from .get_cart import _attach_product_namespace, _attach_market_place_namespace async def get_cart_for_page(session, post_id: int) -> list[CartItem]: """Return cart items scoped to a specific page (via denormalized market_place_container_id).""" ident = current_cart_identity() filters = [ CartItem.deleted_at.is_(None), CartItem.market_place_container_id == post_id, ] if ident["user_id"] is not None: filters.append(CartItem.user_id == ident["user_id"]) else: filters.append(CartItem.session_id == ident["session_id"]) result = await session.execute( select(CartItem) .where(*filters) .order_by(CartItem.created_at.desc()) ) items = list(result.scalars().all()) for ci in items: _attach_product_namespace(ci) _attach_market_place_namespace(ci) return items async def get_calendar_entries_for_page(session, post_id: int): """Return pending calendar entries (DTOs) scoped to a specific page.""" ident = current_cart_identity() params = {"page_id": post_id} if ident["user_id"] is not None: params["user_id"] = ident["user_id"] if ident["session_id"] is not None: params["session_id"] = ident["session_id"] raw = await fetch_data("events", "entries-for-page", params=params, required=False) or [] return [dto_from_dict(CalendarEntryDTO, e) for e in raw] async def get_tickets_for_page(session, post_id: int): """Return reserved tickets (DTOs) scoped to a specific page.""" ident = current_cart_identity() params = {"page_id": post_id} if ident["user_id"] is not None: params["user_id"] = ident["user_id"] if ident["session_id"] is not None: params["session_id"] = ident["session_id"] raw = await fetch_data("events", "tickets-for-page", params=params, required=False) or [] return [dto_from_dict(TicketDTO, t) for t in raw] async def get_cart_grouped_by_page(session) -> list[dict]: """ Load all cart items + calendar entries for the current identity, grouped by market_place (one card per market). Returns a list of dicts: { "post": Post | None, "page_config": PageConfig | None, "market_place": MarketPlace | None, "cart_items": [...], "calendar_entries": [...], "product_count": int, "product_total": float, "calendar_count": int, "calendar_total": float, "total": float, } Calendar entries (no market concept) attach to a page-level group. Items without a market_place go in an orphan bucket (post=None). """ from .get_cart import get_cart from .calendar_cart import get_calendar_cart_entries, get_ticket_cart_entries from .total import total as calc_product_total from .calendar_cart import calendar_total as calc_calendar_total, ticket_total as calc_ticket_total cart_items = await get_cart(session) cal_entries = await get_calendar_cart_entries(session) all_tickets = await get_ticket_cart_entries(session) # Group cart items by market_place_id market_groups: dict[int | None, dict] = {} for ci in cart_items: mp_id = ci.market_place_id if ci.market_place else None if mp_id not in market_groups: market_groups[mp_id] = { "market_place": ci.market_place, "post_id": ci.market_place.container_id if ci.market_place else None, "cart_items": [], "calendar_entries": [], "tickets": [], } market_groups[mp_id]["cart_items"].append(ci) # Attach calendar entries to an existing market group for the same page, # or create a page-level group if no market group exists for that page. page_to_market: dict[int | None, int | None] = {} for mp_id, grp in market_groups.items(): pid = grp["post_id"] if pid is not None and pid not in page_to_market: page_to_market[pid] = mp_id for ce in cal_entries: pid = ce.calendar_container_id or None if pid in page_to_market: market_groups[page_to_market[pid]]["calendar_entries"].append(ce) else: # Create a page-level group for calendar-only entries key = ("cal", pid) if key not in market_groups: market_groups[key] = { "market_place": None, "post_id": pid, "cart_items": [], "calendar_entries": [], "tickets": [], } if pid is not None: page_to_market[pid] = key market_groups[key]["calendar_entries"].append(ce) # Attach tickets to page groups (via calendar_container_id) for tk in all_tickets: pid = tk.calendar_container_id or None if pid in page_to_market: market_groups[page_to_market[pid]]["tickets"].append(tk) else: key = ("tk", pid) if key not in market_groups: market_groups[key] = { "market_place": None, "post_id": pid, "cart_items": [], "calendar_entries": [], "tickets": [], } if pid is not None: page_to_market[pid] = key market_groups[key]["tickets"].append(tk) # Batch-load Post DTOs and PageConfig objects post_ids = list({ grp["post_id"] for grp in market_groups.values() if grp["post_id"] is not None }) posts_by_id: dict[int, object] = {} configs_by_post: dict[int, object] = {} if post_ids: raw_posts = await fetch_data("blog", "posts-by-ids", params={"ids": ",".join(str(i) for i in post_ids)}, required=False) or [] for raw_p in raw_posts: p = dto_from_dict(PostDTO, raw_p) posts_by_id[p.id] = p raw_pcs = await fetch_data("blog", "page-configs-batch", params={"container_type": "page", "ids": ",".join(str(i) for i in post_ids)}, required=False) or [] for raw_pc in raw_pcs: pc = SimpleNamespace(**raw_pc) configs_by_post[pc.container_id] = pc # Build result list (markets with pages first, orphan last) result = [] for _key, grp in sorted( market_groups.items(), key=lambda kv: (kv[1]["post_id"] is None, kv[1]["post_id"] or 0), ): items = grp["cart_items"] entries = grp["calendar_entries"] tks = grp["tickets"] prod_total = calc_product_total(items) or 0 cal_total = calc_calendar_total(entries) or 0 tk_total = calc_ticket_total(tks) or 0 pid = grp["post_id"] result.append({ "post": posts_by_id.get(pid) if pid else None, "page_config": configs_by_post.get(pid) if pid else None, "market_place": grp["market_place"], "cart_items": items, "calendar_entries": entries, "tickets": tks, "product_count": sum(ci.quantity for ci in items), "product_total": prod_total, "calendar_count": len(entries), "calendar_total": cal_total, "ticket_count": len(tks), "ticket_total": tk_total, "total": prod_total + cal_total + tk_total, }) return result