167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
from __future__ import annotations
|
||
|
||
from .shared import *
|
||
from .helpers_roles import _build_role_filter, _has_any_role, _normalize_role_input, _parse_int_list
|
||
from .helpers_user import _collect_user_enrichment, _serialize_user
|
||
|
||
@router.get("/me", response_model=UserResponse)
|
||
async def get_current_user_info(current_user: User = Depends(get_current_active_user)):
|
||
"""当前用户信息。"""
|
||
return UserResponse(**_serialize_user(current_user))
|
||
|
||
@router.get("/")
|
||
async def list_users(
|
||
page: int = Query(1, ge=1),
|
||
page_size: int = Query(20, ge=1, le=100),
|
||
search: Optional[str] = Query(None, description="用户名/姓名/邮箱关键字"),
|
||
role: Optional[str] = Query(None),
|
||
status_value: Optional[str] = Query(None, alias="status"),
|
||
is_active: Optional[bool] = Query(None),
|
||
project_id: Optional[int] = Query(None),
|
||
workload_state: Optional[str] = Query(None, description="idle|normal|overloaded"),
|
||
skill_ids: Optional[str] = Query(None, description="逗号分隔技能ID"),
|
||
tenant_id: int = Depends(get_tenant_id),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""用户分页列表(支持多维过滤)。"""
|
||
_ = current_user
|
||
skill_id_list = _parse_int_list(skill_ids)
|
||
|
||
query = select(User).where(User.tenant_id == tenant_id)
|
||
if role:
|
||
try:
|
||
role_values = _normalize_role_input(role, allow_empty=True)
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc))
|
||
if role_values:
|
||
query = query.where(_build_role_filter(User.role, role_values))
|
||
if status_value:
|
||
query = query.where(User.status == status_value)
|
||
if is_active is not None:
|
||
query = query.where(User.is_active == is_active)
|
||
if search:
|
||
q = f"%{search.strip()}%"
|
||
query = query.where(
|
||
or_(
|
||
User.username.ilike(q),
|
||
User.name.ilike(q),
|
||
User.email.ilike(q),
|
||
)
|
||
)
|
||
|
||
if project_id is not None:
|
||
query = query.join(
|
||
UserProject,
|
||
and_(
|
||
UserProject.user_id == User.id,
|
||
UserProject.tenant_id == tenant_id,
|
||
),
|
||
).where(UserProject.project_id == project_id)
|
||
|
||
if skill_id_list:
|
||
query = query.join(
|
||
UserSkillBinding,
|
||
and_(
|
||
UserSkillBinding.user_id == User.id,
|
||
UserSkillBinding.tenant_id == tenant_id,
|
||
),
|
||
).where(UserSkillBinding.skill_tag_id.in_(skill_id_list))
|
||
|
||
if workload_state:
|
||
query = query.outerjoin(
|
||
UserWorkload,
|
||
and_(
|
||
UserWorkload.user_id == User.id,
|
||
UserWorkload.tenant_id == tenant_id,
|
||
),
|
||
)
|
||
if workload_state == "idle":
|
||
query = query.where(func.coalesce(UserWorkload.workload_ratio, 0.0) < 70.0)
|
||
elif workload_state == "normal":
|
||
query = query.where(
|
||
and_(
|
||
func.coalesce(UserWorkload.workload_ratio, 0.0) >= 70.0,
|
||
func.coalesce(UserWorkload.workload_ratio, 0.0) <= 100.0,
|
||
)
|
||
)
|
||
elif workload_state == "overloaded":
|
||
query = query.where(func.coalesce(UserWorkload.workload_ratio, 0.0) > 100.0)
|
||
|
||
total_result = await db.execute(
|
||
select(func.count()).select_from(query.distinct().subquery())
|
||
)
|
||
total = int(total_result.scalar() or 0)
|
||
|
||
result = await db.execute(
|
||
query.distinct()
|
||
.order_by(User.updated_at.desc(), User.id.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
)
|
||
users = list(result.scalars().all())
|
||
workload_map, top_skills, project_map = await _collect_user_enrichment(db, tenant_id, users)
|
||
|
||
return {
|
||
"items": [
|
||
_serialize_user(
|
||
u,
|
||
workload_map=workload_map,
|
||
top_skills=top_skills,
|
||
project_map=project_map,
|
||
)
|
||
for u in users
|
||
],
|
||
"page": page,
|
||
"page_size": page_size,
|
||
"total": total,
|
||
}
|
||
|
||
@router.get("/available")
|
||
async def get_available_staff(
|
||
role: Optional[str] = Query(None, description="按角色过滤"),
|
||
tenant_id: int = Depends(get_tenant_id),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""获取可分配任务成员(启用 + active)。"""
|
||
_ = current_user
|
||
query = select(User).where(
|
||
and_(
|
||
User.tenant_id == tenant_id,
|
||
User.status == UserStatus.ACTIVE.value,
|
||
User.is_active == True,
|
||
)
|
||
)
|
||
if role:
|
||
try:
|
||
role_values = _normalize_role_input(role, allow_empty=True)
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc))
|
||
if role_values:
|
||
query = query.where(_build_role_filter(User.role, role_values))
|
||
|
||
result = await db.execute(query.order_by(User.workload.asc(), User.id.asc()))
|
||
users = result.scalars().all()
|
||
return [_serialize_user(u) for u in users]
|
||
|
||
@router.get("/{user_id}")
|
||
async def get_user(
|
||
user_id: int,
|
||
tenant_id: int = Depends(get_tenant_id),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""获取单用户详情。"""
|
||
_ = current_user
|
||
result = await db.execute(
|
||
select(User).where(and_(User.tenant_id == tenant_id, User.id == user_id))
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="User not found")
|
||
|
||
workloads, skills, projects = await _collect_user_enrichment(db, tenant_id, [user])
|
||
return _serialize_user(user, workloads, skills, projects)
|
||
|