import logging from datetime import datetime, timezone from decimal import Decimal from typing import Optional from fastapi import APIRouter, HTTPException, Query, status from sqlalchemy.orm import selectinload from sqlmodel import select from app.db import SessionDep from app.dependencies import CurrentUser, get_order_in_organization from app.models.mail_template import MailEventType from app.models.order import Order, OrderPublic, PaymentStatus from app.models.order_line import OrderLine, OrderLinePublic from app.models.purchase_round import PurchaseRound from app.models.route import Route from app.services.order_mail import send_order_event_mail logger = logging.getLogger(__name__) router = APIRouter(prefix="/orders", tags=["orders"]) class OrderLineAdminView(OrderLinePublic): wine_name: str unit_price: Decimal class OrderAdminView(OrderPublic): participant_name: str participant_email: str participant_phone: Optional[str] order_lines: list[OrderLineAdminView] total_price_eur: Decimal def _to_admin_view(order: Order) -> OrderAdminView: order_lines = [ OrderLineAdminView( id=ol.id, order_id=ol.order_id, wine_offering_id=ol.wine_offering_id, quantity=ol.quantity, wine_name=ol.wine_offering.name, unit_price=ol.wine_offering.price, ) for ol in order.order_lines ] total_price_eur = sum((ol.unit_price * ol.quantity for ol in order_lines), Decimal("0")) return OrderAdminView( **OrderPublic.model_validate(order).model_dump(), participant_name=order.participant.name, participant_email=order.participant.email, participant_phone=order.participant.phone, order_lines=order_lines, total_price_eur=total_price_eur, ) @router.get("", response_model=list[OrderAdminView]) def list_orders( session: SessionDep, current_user: CurrentUser, purchase_round_id: Optional[int] = None, route_id: Optional[int] = None, payment_status: Optional[PaymentStatus] = None, limit: int = Query(default=100, le=500, gt=0), offset: int = Query(default=0, ge=0), ) -> list[OrderAdminView]: statement = ( select(Order) .join(PurchaseRound) .join(Route) .where(Route.organization_id == current_user.organization_id) .options(selectinload(Order.participant), selectinload(Order.order_lines).selectinload(OrderLine.wine_offering)) ) if purchase_round_id is not None: statement = statement.where(Order.purchase_round_id == purchase_round_id) if route_id is not None: statement = statement.where(PurchaseRound.route_id == route_id) if payment_status is not None: statement = statement.where(Order.payment_status == payment_status) statement = statement.order_by(Order.id.desc()).offset(offset).limit(limit) orders = session.exec(statement).all() return [_to_admin_view(order) for order in orders] @router.post("/{order_id}/mark-paid", response_model=OrderAdminView) def mark_order_paid(order_id: int, session: SessionDep, current_user: CurrentUser) -> OrderAdminView: order = get_order_in_organization(session, order_id, current_user.organization_id) if order.payment_status == PaymentStatus.PAID: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Order is already marked as paid") order.payment_status = PaymentStatus.PAID order.paid_at = datetime.now(timezone.utc) session.add(order) session.commit() session.refresh(order) try: send_order_event_mail( session, order.purchase_round.route, order.purchase_round, order.participant, order, order.order_lines, MailEventType.PAYMENT_CONFIRMED, ) except Exception: logger.exception("Failed to send payment_confirmed mail for order %s", order.id) return _to_admin_view(order)