Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Phase 1 - Relations service (internal): owns ContainerRelation, exposes get-children data + attach/detach-child actions. Retargeted events, blog, market callers from cart to relations. Phase 2 - Likes service (internal): unified Like model replaces ProductLike and PostLike with generic target_type/target_slug/target_id. Exposes is-liked, liked-slugs, liked-ids data + toggle action. Phase 3 - PageConfig → blog: moved ownership to blog with direct DB queries, removed proxy endpoints from cart. Phase 4 - Orders service (public): owns Order/OrderItem + SumUp checkout flow. Cart checkout now delegates to orders via create-order action. Webhook/return routes and reconciliation moved to orders. Phase 5 - Infrastructure: docker-compose, deploy.sh, Dockerfiles updated for all 3 new services. Added orders_url helper and factory model imports. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
220 lines
8.0 KiB
Python
220 lines
8.0 KiB
Python
"""
|
|
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
|