import logging from typing import Optional from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks from pydantic import BaseModel from sqlalchemy.orm import Session import httpx from config import settings from database import get_db from models.order import Order from schemas.order import OrderOut from routers.deps import get_current_user, require_manager from models.user import User from services.sse_bus import broadcast_sync logger = logging.getLogger(__name__) router = APIRouter() class RejectRequest(BaseModel): reason: Optional[str] = None class StatusUpdateRequest(BaseModel): status: str # "preparing" | "ready" | "out_for_delivery" | "delivered" # ── Helpers ─────────────────────────────────────────────────────────────────── def _get_online_order(order_id: int, db: Session) -> Order: order = db.query(Order).filter(Order.id == order_id, Order.source == "online").first() if not order: raise HTTPException(status_code=404, detail="Online order not found") return order def _cloud_headers() -> dict: return {"X-Site-ID": settings.SITE_ID, "X-Site-Key": settings.SITE_KEY} async def _mirror_status_to_cloud(cloud_order_id: int, new_status: str, rejection_reason: str = None): """Best-effort push of status update to cloud. Failures are logged, not raised.""" if not settings.CLOUD_URL or not cloud_order_id: return try: body = {"status": new_status} if rejection_reason: body["rejection_reason"] = rejection_reason async with httpx.AsyncClient(timeout=10) as client: resp = await client.patch( f"{settings.CLOUD_URL}/api/orders/{cloud_order_id}/status", headers=_cloud_headers(), json=body, ) resp.raise_for_status() logger.info("Mirrored status %s for cloud order id %d", new_status, cloud_order_id) except Exception as e: logger.warning("Failed to mirror status to cloud for order id %d: %s", cloud_order_id, e) # ── Endpoints ───────────────────────────────────────────────────────────────── @router.get("/orders/incoming", response_model=list[OrderOut]) def get_incoming_orders( db: Session = Depends(get_db), user: User = Depends(get_current_user), ): """Return all online orders pending acceptance, newest first.""" orders = ( db.query(Order) .filter(Order.source == "online", Order.online_status == "pending_acceptance") .order_by(Order.opened_at.desc()) .all() ) return orders @router.post("/orders/{order_id}/accept", response_model=OrderOut) async def accept_order( order_id: int, background: BackgroundTasks, db: Session = Depends(get_db), user: User = Depends(require_manager), ): order = _get_online_order(order_id, db) if order.online_status != "pending_acceptance": raise HTTPException(status_code=400, detail=f"Order is already '{order.online_status}'") order.online_status = "accepted" db.commit() db.refresh(order) background.add_task(_mirror_status_to_cloud, order.online_order_cloud_id, "accepted") broadcast_sync("online_order_updated", {"order_id": order_id, "status": "accepted"}) return order @router.post("/orders/{order_id}/reject", response_model=OrderOut) async def reject_order( order_id: int, body: RejectRequest, background: BackgroundTasks, db: Session = Depends(get_db), user: User = Depends(require_manager), ): order = _get_online_order(order_id, db) if order.online_status != "pending_acceptance": raise HTTPException(status_code=400, detail=f"Order is already '{order.online_status}'") order.online_status = "rejected" order.status = "cancelled" db.commit() db.refresh(order) background.add_task(_mirror_status_to_cloud, order.online_order_cloud_id, "rejected", body.reason) broadcast_sync("online_order_updated", {"order_id": order_id, "status": "rejected"}) return order @router.post("/orders/{order_id}/status", response_model=OrderOut) async def update_order_status( order_id: int, body: StatusUpdateRequest, background: BackgroundTasks, db: Session = Depends(get_db), user: User = Depends(require_manager), ): allowed = {"preparing", "ready", "out_for_delivery", "delivered"} if body.status not in allowed: raise HTTPException(status_code=400, detail=f"Invalid status '{body.status}'") order = _get_online_order(order_id, db) order.online_status = body.status if body.status == "delivered": order.status = "closed" db.commit() db.refresh(order) background.add_task(_mirror_status_to_cloud, order.online_order_cloud_id, body.status) broadcast_sync("online_order_updated", {"order_id": order_id, "status": body.status}) return order