from __future__ import annotations from quart import Blueprint, g, redirect, url_for, make_response from sqlalchemy import select, func, or_, cast, String, exists from sqlalchemy.orm import selectinload from shared.models.order import Order, OrderItem from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout from shared.config import config from shared.infrastructure.http_utils import vary as _vary, current_url_without_page as _current_url_without_page from shared.infrastructure.cart_identity import current_cart_identity from bp.cart.services import check_sumup_status from shared.browser.app.utils.htmx import is_htmx_request from shared.sx.helpers import sx_response from bp import register_order from .filters.qs import makeqs_factory, decode def register(url_prefix: str) -> Blueprint: bp = Blueprint("orders", __name__, url_prefix=url_prefix) bp.register_blueprint( register_order(), ) ORDERS_PER_PAGE = 10 # keep in sync with browse page size / your preference oob = { "extends": "_types/root/_index.html", "child_id": "auth-header-child", "header": "_types/auth/header/_header.html", "nav": "_types/auth/_nav.html", "main": "_types/auth/_main_panel.html", } @bp.context_processor def inject_oob(): return {"oob": oob} @bp.before_request def route(): # this is the crucial bit for the |qs filter g.makeqs_factory = makeqs_factory @bp.before_request async def _require_identity(): """Orders require a logged-in user or at least a cart session.""" ident = current_cart_identity() if not ident["user_id"] and not ident["session_id"]: return redirect(url_for("auth.login_form")) @bp.get("/") async def list_orders(): # --- ownership: only show orders belonging to current user/session --- ident = current_cart_identity() if ident["user_id"]: owner_clause = Order.user_id == ident["user_id"] elif ident["session_id"]: owner_clause = Order.session_id == ident["session_id"] else: return redirect(url_for("auth.login_form")) # --- decode filters from query string (page + search) --- q = decode() page, search = q.page, q.search # sanity clamp page if page < 1: page = 1 # --- build where clause for search --- where_clause = None if search: term = f"%{search.strip()}%" conditions = [ Order.status.ilike(term), Order.currency.ilike(term), Order.sumup_checkout_id.ilike(term), Order.sumup_status.ilike(term), Order.description.ilike(term), ] conditions.append( exists( select(1) .select_from(OrderItem) .where( OrderItem.order_id == Order.id, or_( OrderItem.product_title.ilike(term), OrderItem.product_slug.ilike(term), ), ) ) ) # allow exact ID match or partial (string) match try: search_id = int(search) except (TypeError, ValueError): search_id = None if search_id is not None: conditions.append(Order.id == search_id) else: conditions.append(cast(Order.id, String).ilike(term)) where_clause = or_(*conditions) # --- total count & total pages (respecting search + ownership) --- count_stmt = select(func.count()).select_from(Order).where(owner_clause) if where_clause is not None: count_stmt = count_stmt.where(where_clause) total_count_result = await g.s.execute(count_stmt) total_count = total_count_result.scalar_one() or 0 total_pages = max(1, (total_count + ORDERS_PER_PAGE - 1) // ORDERS_PER_PAGE) # clamp page if beyond range (just in case) if page > total_pages: page = total_pages # --- paginated orders (respecting search + ownership) --- offset = (page - 1) * ORDERS_PER_PAGE stmt = ( select(Order) .where(owner_clause) .order_by(Order.created_at.desc()) .offset(offset) .limit(ORDERS_PER_PAGE) ) if where_clause is not None: stmt = stmt.where(where_clause) result = await g.s.execute(stmt) orders = result.scalars().all() from shared.sx.page import get_template_context from sxc.pages.renders import ( render_orders_page, render_orders_rows, render_orders_oob, ) ctx = await get_template_context() qs_fn = makeqs_factory() if not is_htmx_request(): html = await render_orders_page( ctx, orders, page, total_pages, search, total_count, url_for, qs_fn, ) resp = await make_response(html) elif page > 1: sx_src = render_orders_rows( ctx, orders, page, total_pages, url_for, qs_fn, ) resp = sx_response(sx_src) else: sx_src = await render_orders_oob( ctx, orders, page, total_pages, search, total_count, url_for, qs_fn, ) resp = sx_response(sx_src) resp.headers["Hx-Push-Url"] = _current_url_without_page() return _vary(resp) return bp