"""审批流程管理 API。""" from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.core.database import get_db from app.core.security import require_role from app.models.user import User from app.models.workflow import WorkflowDefinition, WorkflowStep from app.schemas.workflow import ( WorkflowCreate, WorkflowUpdate, WorkflowResponse, WorkflowStepCreate, ) router = APIRouter(prefix="/workflows", tags=["审批流程"]) def _serialize_step(step: WorkflowStep) -> dict: return { "id": step.id, "workflow_id": step.workflow_id, "step_number": step.step_number, "name": step.name, "step_type": step.step_type, "approver_role": step.approver_role, "approver_ids": step.approver_ids, "is_required": step.is_required, "timeout_hours": step.timeout_hours, "auto_approve_on_timeout": step.auto_approve_on_timeout, } def _serialize_workflow(wf: WorkflowDefinition) -> dict: return { "id": wf.id, "name": wf.name, "description": wf.description, "is_default": wf.is_default, "is_active": wf.is_active, "created_at": wf.created_at, "steps": [_serialize_step(s) for s in wf.steps], } @router.get("", response_model=List[WorkflowResponse]) async def list_workflows( db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin", "dba")), ): result = await db.execute( select(WorkflowDefinition) .where(WorkflowDefinition.is_active == True) .options(selectinload(WorkflowDefinition.steps)) ) workflows = result.scalars().unique().all() return [_serialize_workflow(wf) for wf in workflows] @router.post("", response_model=WorkflowResponse, status_code=201) async def create_workflow( data: WorkflowCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): # 如果设为默认,取消其他默认 if data.is_default: from sqlalchemy import update as sa_update await db.execute( sa_update(WorkflowDefinition) .where(WorkflowDefinition.is_default == True) .values(is_default=False) ) workflow = WorkflowDefinition( name=data.name, description=data.description, is_default=data.is_default, ) db.add(workflow) await db.flush() for step_data in data.steps: step = WorkflowStep( workflow_id=workflow.id, step_number=step_data.step_number, name=step_data.name, step_type=step_data.step_type, approver_role=step_data.approver_role, approver_ids=step_data.approver_ids, is_required=step_data.is_required, timeout_hours=step_data.timeout_hours, auto_approve_on_timeout=step_data.auto_approve_on_timeout, ) db.add(step) await db.commit() # Reload with steps result = await db.execute( select(WorkflowDefinition) .where(WorkflowDefinition.id == workflow.id) .options(selectinload(WorkflowDefinition.steps)) ) workflow = result.scalar_one() return _serialize_workflow(workflow) @router.get("/{workflow_id}", response_model=WorkflowResponse) async def get_workflow( workflow_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin", "dba")), ): result = await db.execute( select(WorkflowDefinition) .where(WorkflowDefinition.id == workflow_id) .options(selectinload(WorkflowDefinition.steps)) ) workflow = result.scalar_one_or_none() if not workflow: raise HTTPException(status_code=404, detail="审批流程不存在") return _serialize_workflow(workflow) @router.put("/{workflow_id}", response_model=WorkflowResponse) async def update_workflow( workflow_id: int, data: WorkflowUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): result = await db.execute( select(WorkflowDefinition) .where(WorkflowDefinition.id == workflow_id) .options(selectinload(WorkflowDefinition.steps)) ) workflow = result.scalar_one_or_none() if not workflow: raise HTTPException(status_code=404, detail="审批流程不存在") if data.name is not None: workflow.name = data.name if data.description is not None: workflow.description = data.description if data.is_active is not None: workflow.is_active = data.is_active await db.commit() await db.refresh(workflow) return _serialize_workflow(workflow) @router.delete("/{workflow_id}") async def delete_workflow( workflow_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): result = await db.execute( select(WorkflowDefinition).where(WorkflowDefinition.id == workflow_id) ) workflow = result.scalar_one_or_none() if not workflow: raise HTTPException(status_code=404, detail="审批流程不存在") workflow.is_active = False await db.commit() return {"message": "审批流程已删除"} @router.post("/{workflow_id}/set-default") async def set_default_workflow( workflow_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """设为默认审批流程。""" # 取消所有默认 result = await db.execute(select(WorkflowDefinition)) all_workflows = result.scalars().all() for wf in all_workflows: wf.is_default = False # 设置指定流程为默认 result = await db.execute( select(WorkflowDefinition).where(WorkflowDefinition.id == workflow_id) ) workflow = result.scalar_one_or_none() if not workflow: raise HTTPException(status_code=404, detail="审批流程不存在") workflow.is_default = True await db.commit() return {"message": "审批流程 '{}' 已设为默认".format(workflow.name)} @router.post("/{workflow_id}/clone", response_model=WorkflowResponse) async def clone_workflow( workflow_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """克隆审批流程。""" result = await db.execute( select(WorkflowDefinition) .where(WorkflowDefinition.id == workflow_id) .options(selectinload(WorkflowDefinition.steps)) ) source = result.scalar_one_or_none() if not source: raise HTTPException(status_code=404, detail="审批流程不存在") new_workflow = WorkflowDefinition( name="{} (副本)".format(source.name), description=source.description, is_default=False, ) db.add(new_workflow) await db.flush() for step in source.steps: new_step = WorkflowStep( workflow_id=new_workflow.id, step_number=step.step_number, name=step.name, step_type=step.step_type, approver_role=step.approver_role, approver_ids=step.approver_ids, is_required=step.is_required, timeout_hours=step.timeout_hours, auto_approve_on_timeout=step.auto_approve_on_timeout, ) db.add(new_step) await db.commit() # Reload with steps result = await db.execute( select(WorkflowDefinition) .where(WorkflowDefinition.id == new_workflow.id) .options(selectinload(WorkflowDefinition.steps)) ) new_workflow = result.scalar_one() return _serialize_workflow(new_workflow)