"""组织管理模块路由。 提供部门和用户的 CRUD 操作、树形部门结构查询、下级用户递归查询等功能。 支持基于数据权限范围(all/subordinate_only/self_only)的访问控制。 """ import uuid from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from database import get_db from models import Department, User, UserRole, Role from schemas import ( DepartmentCreate, DepartmentUpdate, DepartmentOut, UserCreate, UserUpdate, UserOut, ) from modules.auth.router import hash_password, get_user_roles router = APIRouter(prefix="/api/org", tags=["org"]) # 组织管理模块路由前缀 @router.get("/departments", response_model=list[DepartmentOut]) async def get_departments(request: Request, db: AsyncSession = Depends(get_db)): """获取树形部门结构。 查询所有顶级部门(parent_id 为 NULL),并递归构建完整的部门树。 Args: request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: list[DepartmentOut]: 树形部门结构列表。 """ result = await db.execute( select(Department).where(Department.parent_id.is_(None)).order_by(Department.sort_order) ) roots = result.scalars().all() return [await _build_department_tree(db, d) for d in roots] async def _build_department_tree(db: AsyncSession, dept: Department, _visited: set[uuid.UUID] = None) -> DepartmentOut: """递归构建部门树形结构。 查询当前部门的所有子部门,并递归构建子部门的子部门树。 使用 _visited 集合防止循环引用导致无限递归。 Args: db: 异步数据库会话。 dept: 当前部门对象。 _visited: 已访问的部门 ID 集合,用于防止循环引用。 Returns: DepartmentOut: 包含子部门列表的部门信息。 """ if _visited is None: _visited = set() if dept.id in _visited: # 检测到循环引用,返回不包含子部门的部门信息 return DepartmentOut(id=dept.id, name=dept.name, parent_id=dept.parent_id, path=dept.path, level=dept.level, sort_order=dept.sort_order, children=[]) _visited.add(dept.id) # 查询当前部门的所有子部门 children_result = await db.execute( select(Department).where(Department.parent_id == dept.id).order_by(Department.sort_order) ) children = children_result.scalars().all() return DepartmentOut( id=dept.id, name=dept.name, parent_id=dept.parent_id, path=dept.path, level=dept.level, sort_order=dept.sort_order, children=[await _build_department_tree(db, c, _visited) for c in children], # 递归构建子部门 ) @router.post("/departments", response_model=DepartmentOut) async def create_department( req: DepartmentCreate, request: Request, db: AsyncSession = Depends(get_db) ): """创建新部门。 根据父部门信息计算新部门的层级和路径。 Args: req: 部门创建请求体,包含名称、父部门 ID 和排序权重。 request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: DepartmentOut: 创建后的部门信息。 Raises: HTTPException: 父部门不存在时抛出异常。 """ parent_path = "/" level = 0 if req.parent_id: # 查询父部门信息以计算层级和路径 parent_result = await db.execute(select(Department).where(Department.id == req.parent_id)) parent = parent_result.scalar_one_or_none() if not parent: raise HTTPException(404, "父部门不存在") parent_path = parent.path level = parent.level + 1 # 新部门层级为父部门层级 + 1 dept = Department( name=req.name, parent_id=req.parent_id, path=f"{parent_path}/{req.name}".replace("//", "/"), # 构建部门路径 level=level, sort_order=req.sort_order, ) db.add(dept) await db.flush() return DepartmentOut( id=dept.id, name=dept.name, parent_id=dept.parent_id, path=dept.path, level=dept.level, sort_order=dept.sort_order, children=[], ) @router.put("/departments/{dept_id}", response_model=DepartmentOut) async def update_department( dept_id: uuid.UUID, req: DepartmentUpdate, request: Request, db: AsyncSession = Depends(get_db), ): """更新部门信息。 Args: dept_id: 部门唯一标识 ID。 req: 部门更新请求体,包含可更新的字段。 request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: DepartmentOut: 更新后的部门信息。 Raises: HTTPException: 部门不存在时抛出异常。 """ result = await db.execute(select(Department).where(Department.id == dept_id)) dept = result.scalar_one_or_none() if not dept: raise HTTPException(404, "部门不存在") if req.name is not None: dept.name = req.name if req.parent_id is not None: dept.parent_id = req.parent_id if req.sort_order is not None: dept.sort_order = req.sort_order return DepartmentOut( id=dept.id, name=dept.name, parent_id=dept.parent_id, path=dept.path, level=dept.level, sort_order=dept.sort_order, children=[], ) @router.delete("/departments/{dept_id}") async def delete_department(dept_id: uuid.UUID, request: Request, db: AsyncSession = Depends(get_db)): """删除部门。 Args: dept_id: 部门唯一标识 ID。 request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: dict: 操作结果响应。 Raises: HTTPException: 部门不存在时抛出异常。 """ result = await db.execute(select(Department).where(Department.id == dept_id)) dept = result.scalar_one_or_none() if not dept: raise HTTPException(404, "部门不存在") await db.delete(dept) return {"code": 200, "message": "删除成功"} @router.get("/users", response_model=list[UserOut]) async def get_users(request: Request, db: AsyncSession = Depends(get_db)): """获取用户列表。 根据当前用户的数据权限范围返回不同的用户列表。 Args: request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: list[UserOut]: 用户信息列表。 """ user_ctx = request.state.user result = await db.execute(select(User)) users = result.scalars().all() # 根据数据权限范围过滤用户 if user_ctx["data_scope"] == "self_only": users = [u for u in users if str(u.id) == user_ctx["id"]] elif user_ctx["data_scope"] == "subordinate_only": sub_ids = await _get_subordinate_ids(db, uuid.UUID(user_ctx["id"])) sub_ids.add(uuid.UUID(user_ctx["id"])) users = [u for u in users if u.id in sub_ids] return [await _user_to_out(db, u) for u in users] @router.get("/users/{user_id}", response_model=UserOut) async def get_user(user_id: uuid.UUID, request: Request, db: AsyncSession = Depends(get_db)): """获取指定用户的详细信息。 Args: user_id: 用户唯一标识 ID。 request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: UserOut: 用户详细信息。 Raises: HTTPException: 用户不存在时抛出异常。 """ result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise HTTPException(404, "用户不存在") return await _user_to_out(db, user) @router.post("/users", response_model=UserOut) async def create_user(req: UserCreate, request: Request, db: AsyncSession = Depends(get_db)): """创建新用户。 支持设置用户名、密码、部门、职位、上级用户和角色等信息。 Args: req: 用户创建请求体。 request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: UserOut: 创建后的用户信息。 Raises: HTTPException: 用户名已存在时抛出异常。 """ existing = await db.execute(select(User).where(User.username == req.username)) if existing.scalar_one_or_none(): raise HTTPException(400, "用户名已存在") user = User( username=req.username, password_hash=hash_password(req.password), # 密码哈希存储 display_name=req.display_name, email=req.email, phone=req.phone, wecom_user_id=req.wecom_user_id, department_id=req.department_id, position=req.position, manager_id=req.manager_id, ) db.add(user) await db.flush() if req.role_ids: # 为用户分配角色 for role_id in req.role_ids: db.add(UserRole(user_id=user.id, role_id=role_id)) await db.flush() return await _user_to_out(db, user) @router.put("/users/{user_id}", response_model=UserOut) async def update_user( user_id: uuid.UUID, req: UserUpdate, request: Request, db: AsyncSession = Depends(get_db), ): """更新用户信息。 支持修改显示名称、邮箱、手机号、部门、职位、上级用户、状态和角色。 Args: user_id: 用户唯一标识 ID。 req: 用户更新请求体。 request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: UserOut: 更新后的用户信息。 Raises: HTTPException: 用户不存在时抛出异常。 """ result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise HTTPException(404, "用户不存在") if req.display_name is not None: user.display_name = req.display_name if req.email is not None: user.email = req.email if req.phone is not None: user.phone = req.phone if req.department_id is not None: user.department_id = req.department_id if req.position is not None: user.position = req.position if req.manager_id is not None: user.manager_id = req.manager_id if req.status is not None: user.status = req.status if req.role_ids is not None: # 先删除用户现有角色关联 existing_urs = (await db.execute( select(UserRole).where(UserRole.user_id == user.id) )).scalars().all() for ur in existing_urs: await db.delete(ur) # 重新分配角色 for role_id in req.role_ids: db.add(UserRole(user_id=user.id, role_id=role_id)) return await _user_to_out(db, user) @router.get("/subordinates", response_model=list[UserOut]) async def get_subordinates(request: Request, db: AsyncSession = Depends(get_db)): """获取当前用户的所有下级用户(递归)。 递归查询所有直接或间接以当前用户为上级的用户。 Args: request: HTTP 请求对象,包含当前用户上下文。 db: 异步数据库会话。 Returns: list[UserOut]: 下级用户列表。 """ user_ctx = request.state.user manager_id = uuid.UUID(user_ctx["id"]) sub_ids = await _get_subordinate_ids(db, manager_id) # 递归获取所有下级 ID result = await db.execute(select(User).where(User.id.in_(sub_ids))) users = result.scalars().all() return [await _user_to_out(db, u) for u in users] async def _get_subordinate_ids(db: AsyncSession, manager_id: uuid.UUID, _visited: set[uuid.UUID] = None) -> set[uuid.UUID]: """递归获取指定管理者的所有下级用户 ID。 递归查询直接或间接以指定用户为上级的所有用户 ID。 使用 _visited 集合防止循环引用导致无限递归。 Args: db: 异步数据库会话。 manager_id: 管理者用户 ID。 _visited: 已访问的用户 ID 集合,用于防止循环引用。 Returns: set[uuid.UUID]: 所有下级用户 ID 的集合。 """ if _visited is None: _visited = set() if manager_id in _visited: return set() # 检测到循环引用,返回空集合 _visited.add(manager_id) # 查询直接下级 result = await db.execute(select(User).where(User.manager_id == manager_id)) direct = result.scalars().all() ids = {u.id for u in direct} for sub in direct: ids.update(await _get_subordinate_ids(db, sub.id, _visited)) # 递归获取子级下级 return ids async def _user_to_out(db: AsyncSession, user: User) -> UserOut: """将用户数据库对象转换为 UserOut 响应模型。 Args: db: 异步数据库会话,用于查询用户角色信息。 user: 用户数据库对象。 Returns: UserOut: 用户响应模型,包含角色列表。 """ roles = await get_user_roles(db, user.id) # 获取用户角色信息 return UserOut( id=user.id, username=user.username, display_name=user.display_name, email=user.email, phone=user.phone, wecom_user_id=user.wecom_user_id, department_id=user.department_id, position=user.position, manager_id=user.manager_id, status=user.status, roles=roles, created_at=user.created_at, )