PMFlow/backend/app/services/workload/board_queries_a.py

186 lines
5.7 KiB
Python

from __future__ import annotations
from .shared import *
async def get_team_workload_board(
db: AsyncSession,
tenant_id: int = 1,
project_id: Optional[int] = None,
) -> list[dict]:
"""团队负载看板数据。"""
query = (
select(UserWorkload, User)
.join(User, UserWorkload.user_id == User.id)
.where(
and_(
UserWorkload.tenant_id == tenant_id,
User.tenant_id == tenant_id,
)
)
)
if project_id is not None:
query = query.join(
UserProject,
and_(
UserProject.tenant_id == tenant_id,
UserProject.user_id == User.id,
),
).where(UserProject.project_id == project_id)
query = query.order_by(UserWorkload.workload_ratio.desc(), User.id.asc())
rows = (await db.execute(query)).all()
return [
{
"user_id": str(w.user_id),
"user_name": u.name,
"username": u.username,
"role": u.role,
"is_active": u.is_active,
"current_assigned_hours": float(w.current_assigned_hours or 0),
"max_weekly_hours": float(w.max_available_hours_per_week or 40),
"workload_ratio": float(w.workload_ratio or 0),
"remaining_hours": round(
max(
0.0,
float(w.max_available_hours_per_week or 40)
- float(w.current_assigned_hours or 0),
),
2,
),
"updated_at": w.updated_at.isoformat() if w.updated_at else None,
}
for w, u in rows
]
async def get_user_personal_workload(
db: AsyncSession,
tenant_id: int,
user_id: int,
) -> Optional[dict]:
"""个人负载详情。"""
row = (
await db.execute(
select(UserWorkload, User)
.join(User, UserWorkload.user_id == User.id)
.where(
and_(
UserWorkload.tenant_id == tenant_id,
UserWorkload.user_id == user_id,
User.tenant_id == tenant_id,
)
)
)
).first()
if row:
w, u = row
return {
"user_id": str(u.id),
"user_name": u.name,
"username": u.username,
"is_active": u.is_active,
"current_assigned_hours": float(w.current_assigned_hours or 0),
"max_weekly_hours": float(w.max_available_hours_per_week or 40),
"workload_ratio": float(w.workload_ratio or 0),
"remaining_hours": round(
max(
0.0,
float(w.max_available_hours_per_week or 40)
- float(w.current_assigned_hours or 0),
),
2,
),
"updated_at": w.updated_at.isoformat() if w.updated_at else None,
}
user = (
await db.execute(
select(User).where(and_(User.tenant_id == tenant_id, User.id == user_id))
)
).scalar_one_or_none()
if not user:
return None
workload = await ensure_user_workload(db, tenant_id, user_id)
await db.commit()
return {
"user_id": str(user.id),
"user_name": user.name,
"username": user.username,
"is_active": user.is_active,
"current_assigned_hours": float(workload.current_assigned_hours or 0),
"max_weekly_hours": float(workload.max_available_hours_per_week or 40),
"workload_ratio": float(workload.workload_ratio or 0),
"remaining_hours": round(
max(
0.0,
float(workload.max_available_hours_per_week or 40)
- float(workload.current_assigned_hours or 0),
),
2,
),
"updated_at": workload.updated_at.isoformat() if workload.updated_at else None,
}
async def update_user_max_weekly_hours(
db: AsyncSession,
tenant_id: int,
user_id: int,
max_weekly_hours: float,
) -> Optional[dict]:
"""更新每周可用工时并重算负载率。"""
user = (
await db.execute(
select(User).where(and_(User.tenant_id == tenant_id, User.id == user_id))
)
).scalar_one_or_none()
if not user:
return None
workload = await ensure_user_workload(db, tenant_id, user_id)
workload.max_available_hours_per_week = max(1.0, float(max_weekly_hours))
await recalculate_user_workload_ratio(db, tenant_id, user_id, commit=False)
await db.commit()
await db.refresh(workload)
return {
"user_id": str(user.id),
"max_weekly_hours": float(workload.max_available_hours_per_week),
"workload_ratio": float(workload.workload_ratio or 0),
}
async def list_user_assigned_tasks(
db: AsyncSession,
tenant_id: int,
user_id: int,
*,
status: Optional[str] = None,
priority: Optional[str] = None,
) -> list[dict]:
"""查询用户任务列表。"""
query = select(Task).where(
and_(
Task.tenant_id == tenant_id,
Task.assignee_id == user_id,
)
)
if status:
query = query.where(Task.status == status)
if priority:
query = query.where(Task.priority == priority)
rows = (await db.execute(query.order_by(Task.updated_at.desc()))).scalars().all()
return [
{
"id": str(t.id),
"title": t.title,
"project_id": str(t.project_id),
"priority": t.priority,
"status": t.status,
"estimated_hours": float(t.estimated_hours or 0),
"due_date": t.due_date.isoformat() if t.due_date else None,
}
for t in rows
]