from __future__ import annotations from quart import Blueprint, g, render_template, redirect, url_for, make_response from sqlalchemy import select, func, or_, cast, String, exists from sqlalchemy.orm import selectinload from models.market import Product from models.order import Order, OrderItem from suma_browser.app.payments.sumup import create_checkout as sumup_create_checkout from config import config from shared.http_utils import vary as _vary, current_url_without_page as _current_url_without_page from suma_browser.app.bp.cart.services import check_sumup_status from suma_browser.app.utils.htmx import is_htmx_request from suma_browser.app.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 @bp.before_request def route(): # this is the crucial bit for the |qs filter g.makeqs_factory = makeqs_factory @bp.get("/") async def list_orders(): # --- 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) .join(Product, Product.id == OrderItem.product_id) .where( OrderItem.order_id == Order.id, or_( OrderItem.product_title.ilike(term), Product.title.ilike(term), Product.description_short.ilike(term), Product.description_html.ilike(term), Product.slug.ilike(term), Product.brand.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) --- count_stmt = select(func.count()).select_from(Order) 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) --- offset = (page - 1) * ORDERS_PER_PAGE stmt = ( select(Order) .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() context = { "orders": orders, "page": page, "total_pages": total_pages, "search": search, "search_count": total_count, # For search display } # Determine which template to use based on request type and pagination if not is_htmx_request(): # Normal browser request: full page with layout html = await render_template("_types/orders/index.html", **context) elif page > 1: # HTMX pagination: just table rows + sentinel html = await render_template("_types/orders/_rows.html", **context) else: # HTMX navigation (page 1): main panel + OOB elements html = await render_template("_types/orders/_oob_elements.html", **context) resp = await make_response(html) resp.headers["Hx-Push-Url"] = _current_url_without_page() return _vary(resp) return bp