from __future__ import annotations import json from typing import Annotated from uuid import uuid4 from fastapi import APIRouter, Depends, Header, HTTPException, Query, status from sqlalchemy.orm import Session from app.core.constants import ERROR_CODE_OK from app.core.config import get_settings from app.core.time import format_now from app.db.session import get_db from app.schemas.approval import ( ApprovalDecisionRequest, ApprovalDetailData, ApprovalListData, CreateApprovalData, CreateApprovalRequest, ) from app.schemas.common import ApiResponse from app.services.approval_service import ApprovalConflictError, ApprovalNotFoundError, ApprovalService router = APIRouter(prefix="/api/demo/approval", tags=["demo-approval"]) def build_request_id(header_value: str | None) -> str: return header_value or f"req-{uuid4().hex[:12]}" def to_detail_data(approval) -> ApprovalDetailData: return ApprovalDetailData( approval_id=approval.approval_id, task_id=approval.task_id, approval_status=approval.approval_status, risk_level=approval.risk_level, approvers=json.loads(approval.approver_user_ids_json), reason=approval.reason, created_at=approval.created_at, updated_at=approval.updated_at, ) @router.post("/requests", response_model=ApiResponse[CreateApprovalData]) def create_request( payload: CreateApprovalRequest, db: Annotated[Session, Depends(get_db)], x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None, ) -> ApiResponse[CreateApprovalData]: request_id = build_request_id(x_request_id) approval = ApprovalService(db, get_settings().default_timezone).create_request(payload) return ApiResponse[CreateApprovalData]( request_id=request_id, success=True, code=ERROR_CODE_OK, message="approval created", data=CreateApprovalData(approval_id=approval.approval_id, approval_status=approval.approval_status), timestamp=format_now(get_settings().default_timezone), ) @router.get("/requests/{approval_id}", response_model=ApiResponse[ApprovalDetailData]) def get_request( approval_id: str, db: Annotated[Session, Depends(get_db)], x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None, ) -> ApiResponse[ApprovalDetailData]: request_id = build_request_id(x_request_id) service = ApprovalService(db, get_settings().default_timezone) try: approval = service.get_request(approval_id) except ApprovalNotFoundError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={"code": exc.code, "message": "approval not found"}) from exc return ApiResponse[ApprovalDetailData]( request_id=request_id, success=True, code=ERROR_CODE_OK, message="success", data=to_detail_data(approval), timestamp=format_now(get_settings().default_timezone), ) @router.post("/requests/{approval_id}/decision", response_model=ApiResponse[ApprovalDetailData]) def decide_request( approval_id: str, payload: ApprovalDecisionRequest, db: Annotated[Session, Depends(get_db)], x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None, ) -> ApiResponse[ApprovalDetailData]: request_id = build_request_id(x_request_id) service = ApprovalService(db, get_settings().default_timezone) try: approval = service.decide(approval_id, payload) except ApprovalNotFoundError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={"code": exc.code, "message": "approval not found"}) from exc except ApprovalConflictError as exc: message = exc.args[0] if exc.args else "approval conflict" raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail={"code": exc.code, "message": message}) from exc return ApiResponse[ApprovalDetailData]( request_id=request_id, success=True, code=ERROR_CODE_OK, message="success", data=to_detail_data(approval), timestamp=format_now(get_settings().default_timezone), ) @router.get("/requests", response_model=ApiResponse[ApprovalListData]) def list_pending_requests( approval_status: str = Query(default="PENDING"), approver_user_id: str | None = Query(default=None), db: Annotated[Session, Depends(get_db)] = None, x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None, ) -> ApiResponse[ApprovalListData]: request_id = build_request_id(x_request_id) if approval_status != "PENDING": raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail={"code": "INVALID_PARAM", "message": "当前仅支持查询 PENDING 审批单"}) approvals = ApprovalService(db, get_settings().default_timezone).list_pending(approver_user_id) return ApiResponse[ApprovalListData]( request_id=request_id, success=True, code=ERROR_CODE_OK, message="success", data=ApprovalListData(approvals=[to_detail_data(item) for item in approvals]), timestamp=format_now(get_settings().default_timezone), )