197 lines
7.3 KiB
Python
197 lines
7.3 KiB
Python
"""
|
||
领导团队管理 API(role=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(),
|
||
)
|