huoyan-enterprise/backend/api/team_router.py

197 lines
7.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
领导团队管理 APIrole=leader 或 admin 可访问)
路由前缀:/api/team
接口列表:
GET /api/team/members 查看本部门及下级成员
GET /api/team/members/{user_id} 查看成员详情
PATCH /api/team/members/{user_id}/permissions 修改成员上传权限
GET /api/team/audit-logs 查看本部门及下级操作日志
"""
from typing import List, Optional
import asyncpg
from fastapi import APIRouter, Depends, HTTPException, Query, status
from admin.schemas import AuditLogItem, AuditLogListResponse, TeamMemberItem, UserPermissionUpdate
from core.dependencies import get_db, get_current_user
from core.permissions import get_managed_dept_ids, is_subordinate
from models.user import User
from services.audit_service import AuditService
from utils.helpers import BaseResponse
from logger.logging import get_logger
logger = get_logger(__name__)
team_router = APIRouter(prefix="/api/team", tags=["领导团队管理"])
async def _require_leader_or_admin(user: User) -> User:
"""只有 leader 或 admin 才能访问团队管理接口。"""
if user.role not in ("leader", "admin"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="仅部门领导或管理员可访问团队管理功能",
)
return user
# ─────────────────────────────────────────────
# 成员列表
# ─────────────────────────────────────────────
@team_router.get("/members", response_model=BaseResponse, summary="查看管辖范围内的成员")
async def list_team_members(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user),
conn: asyncpg.Connection = Depends(get_db),
):
await _require_leader_or_admin(current_user)
dept_ids = await get_managed_dept_ids(conn, current_user)
if not dept_ids:
return BaseResponse(code=200, msg="ok", data={"total": 0, "items": []})
offset = (page - 1) * page_size
total = await conn.fetchval(
"""
SELECT COUNT(*) FROM user_list
WHERE enterprise_id = $1
AND department_id = ANY($2::int[])
AND id != $3
""",
current_user.enterprise_id, dept_ids, current_user.id,
)
rows = await conn.fetch(
"""
SELECT u.id, u.username, u.display_name, u.department_id,
d.name AS department_name, u.role, u.is_active,
u.allow_kb_upload, u.created_at
FROM user_list u
LEFT JOIN department d ON d.id = u.department_id
WHERE u.enterprise_id = $1
AND u.department_id = ANY($2::int[])
AND u.id != $3
ORDER BY u.department_id, u.id
LIMIT $4 OFFSET $5
""",
current_user.enterprise_id, dept_ids, current_user.id, page_size, offset,
)
items = [TeamMemberItem(**dict(r)).model_dump() for r in rows]
return BaseResponse(code=200, msg="ok", data={"total": int(total or 0), "items": items})
@team_router.get("/members/{user_id}", response_model=BaseResponse, summary="查看成员详情")
async def get_team_member(
user_id: int,
current_user: User = Depends(get_current_user),
conn: asyncpg.Connection = Depends(get_db),
):
await _require_leader_or_admin(current_user)
if not await is_subordinate(conn, current_user, user_id):
raise HTTPException(status_code=403, detail="无权查看该成员,不在管辖范围内")
row = await conn.fetchrow(
"""
SELECT u.id, u.username, u.display_name, u.department_id,
d.name AS department_name, u.role, u.is_active,
u.allow_kb_upload, u.created_at
FROM user_list u
LEFT JOIN department d ON d.id = u.department_id
WHERE u.id = $1
""",
user_id,
)
if not row:
raise HTTPException(status_code=404, detail="用户不存在")
return BaseResponse(code=200, msg="ok", data=TeamMemberItem(**dict(row)).model_dump())
# ─────────────────────────────────────────────
# 修改成员权限
# ─────────────────────────────────────────────
@team_router.patch(
"/members/{user_id}/permissions",
response_model=BaseResponse,
summary="设置下属成员知识库上传权限",
)
async def update_member_permission(
user_id: int,
body: UserPermissionUpdate,
current_user: User = Depends(get_current_user),
conn: asyncpg.Connection = Depends(get_db),
):
await _require_leader_or_admin(current_user)
if not await is_subordinate(conn, current_user, user_id):
raise HTTPException(status_code=403, detail="越级操作:该用户不在您的管辖范围内")
await conn.execute(
"UPDATE user_list SET allow_kb_upload = $1 WHERE id = $2",
body.allow_kb_upload, user_id,
)
# 审计日志
target_row = await conn.fetchrow(
"SELECT department_id FROM user_list WHERE id = $1", user_id
)
await AuditService.write(
conn,
enterprise_id=current_user.enterprise_id or 0,
actor_id=current_user.id,
action="permission_change",
target_user_id=user_id,
department_id=current_user.department_id,
metadata={
"field": "allow_kb_upload",
"new": body.allow_kb_upload,
"by_role": current_user.role,
},
)
return BaseResponse(
code=200,
msg="权限已更新",
data={"user_id": user_id, "allow_kb_upload": body.allow_kb_upload},
)
# ─────────────────────────────────────────────
# 审计日志(部门范围)
# ─────────────────────────────────────────────
@team_router.get(
"/audit-logs",
response_model=BaseResponse,
summary="查看管辖部门内的操作日志",
)
async def list_team_audit_logs(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
action: Optional[str] = Query(None, description="操作类型过滤"),
actor_id: Optional[int] = Query(None, description="指定操作者"),
kb_id: Optional[int] = Query(None, description="指定知识库"),
current_user: User = Depends(get_current_user),
conn: asyncpg.Connection = Depends(get_db),
):
await _require_leader_or_admin(current_user)
dept_ids = await get_managed_dept_ids(conn, current_user)
# admin 传 None 查全量leader 传 dept_ids
filter_depts = None if current_user.role == "admin" else dept_ids
rows, total = await AuditService.list_logs(
conn,
current_user.enterprise_id,
department_ids=filter_depts,
actor_id=actor_id,
kb_id=kb_id,
action=action,
page=page,
page_size=page_size,
)
items = [AuditLogItem(**r).model_dump() for r in rows]
return BaseResponse(
code=200,
msg="ok",
data=AuditLogListResponse(total=total, items=items).model_dump(),
)