Files
mono/cart/bp/cart/services/page_cart.py
giles f42042ccb7 Monorepo: consolidate 7 repos into one
Combines shared, blog, market, cart, events, federation, and account
into a single repository. Eliminates submodule sync, sibling model
copying at build time, and per-app CI orchestration.

Changes:
- Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs
- Remove stale sibling model copies from each app
- Update all 6 Dockerfiles for monorepo build context (root = .)
- Add build directives to docker-compose.yml
- Add single .gitea/workflows/ci.yml with change detection
- Add .dockerignore for monorepo build context
- Create __init__.py for federation and account (cross-app imports)
2026-02-24 19:44:17 +00:00

213 lines
7.5 KiB
Python

"""
Page-scoped cart queries.
Groups cart items and calendar entries by their owning page (Post),
determined via CartItem.market_place.container_id and CalendarEntry.calendar.container_id
(where container_type == "page").
"""
from __future__ import annotations
from collections import defaultdict
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from shared.models.market import CartItem
from shared.models.market_place import MarketPlace
from shared.models.page_config import PageConfig
from shared.services.registry import services
from .identity import current_cart_identity
async def get_cart_for_page(session, post_id: int) -> list[CartItem]:
"""Return cart items scoped to a specific page (via MarketPlace.container_id)."""
ident = current_cart_identity()
filters = [
CartItem.deleted_at.is_(None),
MarketPlace.container_type == "page",
MarketPlace.container_id == post_id,
MarketPlace.deleted_at.is_(None),
]
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)
.join(MarketPlace, CartItem.market_place_id == MarketPlace.id)
.where(*filters)
.order_by(CartItem.created_at.desc())
.options(
selectinload(CartItem.product),
selectinload(CartItem.market_place),
)
)
return result.scalars().all()
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()
return await services.calendar.entries_for_page(
session, post_id,
user_id=ident["user_id"],
session_id=ident["session_id"],
)
async def get_tickets_for_page(session, post_id: int):
"""Return reserved tickets (DTOs) scoped to a specific page."""
ident = current_cart_identity()
return await services.calendar.tickets_for_page(
session, post_id,
user_id=ident["user_id"],
session_id=ident["session_id"],
)
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, PageConfig] = {}
if post_ids:
for p in await services.blog.get_posts_by_ids(session, post_ids):
posts_by_id[p.id] = p
pc_result = await session.execute(
select(PageConfig).where(
PageConfig.container_type == "page",
PageConfig.container_id.in_(post_ids),
)
)
for pc in pc_result.scalars().all():
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