186 lines
5.7 KiB
Python
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
|
|
]
|
|
|