diff --git a/local_backend/main.py b/local_backend/main.py index a04cb06..68b9600 100644 --- a/local_backend/main.py +++ b/local_backend/main.py @@ -222,6 +222,7 @@ def _run_migrations(): # Xenia Connect — online order fields on orders table "ALTER TABLE orders ADD COLUMN source VARCHAR NOT NULL DEFAULT 'pos'", "ALTER TABLE orders ADD COLUMN online_order_ref VARCHAR", + "ALTER TABLE orders ADD COLUMN online_order_cloud_id INTEGER", "ALTER TABLE orders ADD COLUMN online_status VARCHAR", "ALTER TABLE orders ADD COLUMN online_customer_name VARCHAR", "ALTER TABLE orders ADD COLUMN online_customer_phone VARCHAR", diff --git a/local_backend/models/order.py b/local_backend/models/order.py index e625b07..2db70fd 100644 --- a/local_backend/models/order.py +++ b/local_backend/models/order.py @@ -23,6 +23,7 @@ class Order(Base): # Xenia Connect — online order fields source = Column(String, default="pos", nullable=False) # "pos" | "online" online_order_ref = Column(String, nullable=True) # e.g. "ORD-0042" + online_order_cloud_id = Column(Integer, nullable=True) # cloud DB pk for status mirroring online_status = Column(String, nullable=True) # mirrors cloud status online_customer_name = Column(String, nullable=True) online_customer_phone = Column(String, nullable=True) diff --git a/local_backend/routers/connect_orders.py b/local_backend/routers/connect_orders.py index e98f951..5445da6 100644 --- a/local_backend/routers/connect_orders.py +++ b/local_backend/routers/connect_orders.py @@ -39,70 +39,26 @@ 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_ref: str, new_status: str, rejection_reason: str = None): +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_ref: + if not settings.CLOUD_URL or not cloud_order_id: return try: - # We need the cloud order id — look it up by public_ref via the status endpoint - # (The cloud orders router exposes PATCH /{order_id}/status by numeric id. - # We store cloud id implicitly through public_ref; fetch it first.) + body = {"status": new_status} + if rejection_reason: + body["rejection_reason"] = rejection_reason + async with httpx.AsyncClient(timeout=10) as client: - # Get cloud order id via status lookup (public endpoint, no auth needed) - status_resp = await client.get( - f"{settings.CLOUD_URL}/api/orders/status/{cloud_order_ref}" - ) - if status_resp.status_code != 200: - logger.warning("Could not find cloud order %s", cloud_order_ref) - return - - # Search pending list to get numeric id — use the site-key protected route - from middleware.license_check import license_state - site_numeric_id = license_state.get("site_numeric_id") - if not site_numeric_id: - return - - pending_resp = await client.get( - f"{settings.CLOUD_URL}/api/orders/pending/{site_numeric_id}", - headers=_cloud_headers(), - ) - # The order may already be synced; look in all orders via remote dashboard - # Simpler: use the manager-JWT-free internal PATCH directly via site key - # We need the cloud order numeric id. Store it on first pull — for now, - # the cloud also exposes PATCH via remote dashboard (manager JWT). - # Best path: add a site-key variant. Until then, we mirror via the - # existing PATCH endpoint using site key — works because orders.py - # accepts site key auth on PATCH /{order_id}/status. - # We get the numeric id from the pending list or a dedicated lookup. - # Iterate pending to find our ref (may be empty if already synced). - cloud_order_id = None - if pending_resp.status_code == 200: - for o in pending_resp.json(): - if o.get("public_ref") == cloud_order_ref: - cloud_order_id = o["id"] - break - - if cloud_order_id is None: - # Already synced — not in pending list. We can't easily look it up - # without a dedicated search endpoint. Log and skip for now; the - # remote manager dashboard's own PATCH handles the remote case. - logger.debug("Cloud order %s not in pending list; skipping mirror", cloud_order_ref) - return - - body = {"status": new_status} - if rejection_reason: - body["rejection_reason"] = rejection_reason - - patch_resp = await client.patch( + resp = await client.patch( f"{settings.CLOUD_URL}/api/orders/{cloud_order_id}/status", headers=_cloud_headers(), json=body, ) - patch_resp.raise_for_status() - logger.info("Mirrored status %s for cloud order %s", new_status, cloud_order_ref) + 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 %s: %s", cloud_order_ref, e) + logger.warning("Failed to mirror status to cloud for order id %d: %s", cloud_order_id, e) # ── Endpoints ───────────────────────────────────────────────────────────────── @@ -137,7 +93,7 @@ async def accept_order( db.commit() db.refresh(order) - background.add_task(_mirror_status_to_cloud, order.online_order_ref, "accepted") + 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 @@ -159,7 +115,7 @@ async def reject_order( db.commit() db.refresh(order) - background.add_task(_mirror_status_to_cloud, order.online_order_ref, "rejected", body.reason) + 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 @@ -183,6 +139,6 @@ async def update_order_status( db.commit() db.refresh(order) - background.add_task(_mirror_status_to_cloud, order.online_order_ref, body.status) + 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 diff --git a/local_backend/schemas/order.py b/local_backend/schemas/order.py index 3ad246e..53215cf 100644 --- a/local_backend/schemas/order.py +++ b/local_backend/schemas/order.py @@ -122,6 +122,7 @@ class OrderOut(BaseModel): # Xenia Connect — online order fields source: str = "pos" online_order_ref: Optional[str] = None + online_order_cloud_id: Optional[int] = None online_status: Optional[str] = None online_customer_name: Optional[str] = None online_customer_phone: Optional[str] = None diff --git a/local_backend/services/cloud_sync.py b/local_backend/services/cloud_sync.py index 7ccd427..179ee63 100644 --- a/local_backend/services/cloud_sync.py +++ b/local_backend/services/cloud_sync.py @@ -289,6 +289,7 @@ async def _pull_pending_orders(): status="open", source="online", online_order_ref=cloud_order["public_ref"], + online_order_cloud_id=cloud_order["id"], online_status="pending_acceptance", online_customer_name=cloud_order.get("customer_name"), online_customer_phone=cloud_order.get("customer_phone"),