from __future__ import annotations from typing import Any, Literal from uuid import UUID from fastapi import APIRouter, Query from pydantic import BaseModel, Field from sqlalchemy import select from bid_doctor.core.db import tenant_tx from bid_doctor.document.models import SourceDocument from bid_doctor.identity.deps import Ctx from bid_doctor.project import service, workbench router = APIRouter(tags=["projects"]) class ProjectIn(BaseModel): name: str | None = Field(default=None, max_length=200) class TransitionIn(BaseModel): action: Literal["submit", "unsubmit", "archive", "unarchive"] @router.get("/projects") async def list_projects(ctx: Ctx, limit: int = Query(default=20, ge=1, le=100)) -> dict[str, Any]: # TODO(工作台筛选):status / deadline / q / 游标分页,按 openapi.yaml 补齐 async with tenant_tx(ctx) as s: return {"items": await workbench.rows(s, ctx, limit), "next_cursor": None} @router.post("/projects", status_code=201) async def create_project(body: ProjectIn, ctx: Ctx) -> dict[str, Any]: async with tenant_tx(ctx) as s: p = await service.create(s, ctx, body.name) return await workbench.detail(s, ctx, p.id) @router.get("/projects/{project_id}") async def get_project(project_id: UUID, ctx: Ctx) -> dict[str, Any]: async with tenant_tx(ctx) as s: return await workbench.detail(s, ctx, project_id) @router.post("/projects/{project_id}/transitions") async def transition_project(project_id: UUID, body: TransitionIn, ctx: Ctx) -> dict[str, Any]: async with tenant_tx(ctx) as s: parsed = ( await s.execute( select(SourceDocument.id) .where( SourceDocument.project_id == project_id, SourceDocument.role == "tender", SourceDocument.text_layer_key.is_not(None), ) .limit(1) ) ).scalar_one_or_none() is not None await service.transition(s, ctx, project_id, body.action, parsed) return await workbench.detail(s, ctx, project_id)