"""Drafts router — exposes bot_drafts to frontend for Wizard pre-population and bi-directional sync.""" from typing import Any from uuid import UUID from fastapi import APIRouter, Depends from pydantic import BaseModel from sqlalchemy import func, select, update from sqlalchemy.ext.asyncio import AsyncSession from app.db.engine import get_db from app.models.draft import BotDraft from app.utils.errors import NotFoundError router = APIRouter(prefix="/drafts", tags=["drafts"]) class DraftUpdateIn(BaseModel): payload: dict[str, Any] | None = None status: str | None = None class DraftCommitIn(BaseModel): quote_id: str | None = None def _serialize_draft(draft: BotDraft) -> dict: return { "id": str(draft.id), "chat_id": draft.chat_id, "kind": draft.kind, "status": draft.status, "payload": draft.payload or {}, "missing": draft.missing or [], "source": draft.source, "review_message_id": draft.review_message_id, "last_bale_update_at": draft.last_bale_update_at.isoformat() if draft.last_bale_update_at else None, "created_at": draft.created_at.isoformat() if draft.created_at else None, "updated_at": draft.updated_at.isoformat() if draft.updated_at else None, } @router.get("/{draft_id}") async def get_draft(draft_id: UUID, db: AsyncSession = Depends(get_db)): stmt = select(BotDraft).where(BotDraft.id == draft_id) draft = (await db.execute(stmt)).scalar_one_or_none() if not draft: raise NotFoundError("پیش‌نویس استعلام یافت نشد") return {"data": _serialize_draft(draft)} @router.patch("/{draft_id}") async def update_draft(draft_id: UUID, body: DraftUpdateIn, db: AsyncSession = Depends(get_db)): stmt = select(BotDraft).where(BotDraft.id == draft_id) draft = (await db.execute(stmt)).scalar_one_or_none() if not draft: raise NotFoundError("پیش‌نویس استعلام یافت نشد") if body.payload is not None: merged = dict(draft.payload or {}) merged.update(body.payload) draft.payload = merged if body.status is not None: draft.status = body.status draft.updated_at = func.now() await db.commit() await db.refresh(draft) return {"data": _serialize_draft(draft)} @router.post("/{draft_id}/commit") async def commit_draft(draft_id: UUID, body: DraftCommitIn, db: AsyncSession = Depends(get_db)): stmt = select(BotDraft).where(BotDraft.id == draft_id) draft = (await db.execute(stmt)).scalar_one_or_none() if not draft: raise NotFoundError("پیش‌نویس استعلام یافت نشد") draft.status = "committed" payload = dict(draft.payload or {}) if body.quote_id: payload["committed_quote_id"] = body.quote_id draft.payload = payload draft.updated_at = func.now() await db.commit() await db.refresh(draft) return {"data": _serialize_draft(draft)} @router.post("/{draft_id}/cancel") async def cancel_draft(draft_id: UUID, db: AsyncSession = Depends(get_db)): stmt = select(BotDraft).where(BotDraft.id == draft_id) draft = (await db.execute(stmt)).scalar_one_or_none() if not draft: raise NotFoundError("پیش‌نویس استعلام یافت نشد") draft.status = "cancelled" draft.updated_at = func.now() await db.commit() await db.refresh(draft) return {"data": _serialize_draft(draft)}