The Phase 4 _mirror_status_to_cloud function had no way to look up the
cloud order's numeric id once it was marked synced (not in the pending
list anymore), so status updates from local staff could silently fail.
Fix:
- models/order.py: online_order_cloud_id INTEGER column added to Order
- main.py: migration for the new column
- schemas/order.py: online_order_cloud_id exposed in OrderOut
- cloud_sync.py: stores cloud_order["id"] as online_order_cloud_id
when creating the local order during the pull
- connect_orders.py: _mirror_status_to_cloud now takes the integer
cloud id directly — no more pending-list lookup; function body
reduced from ~50 lines to ~15
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
5.0 KiB
Python
145 lines
5.0 KiB
Python
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
|