132 lines
4.2 KiB
Python
132 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
from ...services.sstc.entity_lookup import _ensure_project, _ensure_task
|
|
from ...services.sstc.governance import _require_manager
|
|
from ...services.sstc.signal_core import _parse_node_id, _parse_project_id
|
|
|
|
router = APIRouter(tags=["sstc"])
|
|
|
|
@router.post("/projects/{project_id}/tasks/{task_id}/recommend-assignee")
|
|
async def recommend_assignee_for_task(
|
|
project_id: str,
|
|
task_id: str,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tid = _parse_node_id(task_id, "task_id")
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
|
|
task = await _ensure_task(db, tenant_id=tenant_id, project_id=pid, task_id=tid)
|
|
|
|
required_skills = [str(x) for x in (task.required_skill_tag_ids or [])]
|
|
recommendations = await recommend_personnel(
|
|
db=db,
|
|
tenant_id=tenant_id,
|
|
required_skills=required_skills,
|
|
task_description=task.description or task.title,
|
|
task_id=str(task.id),
|
|
limit=5,
|
|
)
|
|
|
|
project_user_ids_result = await db.execute(
|
|
select(UserProject.user_id).where(
|
|
and_(
|
|
UserProject.tenant_id == tenant_id,
|
|
UserProject.project_id == pid,
|
|
)
|
|
)
|
|
)
|
|
project_user_ids = {int(uid) for uid in project_user_ids_result.scalars().all()}
|
|
|
|
active_users_result = await db.execute(
|
|
select(User.id).where(
|
|
and_(
|
|
User.tenant_id == tenant_id,
|
|
User.status == "active",
|
|
User.is_active.is_(True),
|
|
)
|
|
)
|
|
)
|
|
active_user_ids = {int(uid) for uid in active_users_result.scalars().all()}
|
|
|
|
filtered = []
|
|
for item in recommendations.get("recommendations", []):
|
|
try:
|
|
uid = int(item.get("user_id"))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if uid in project_user_ids and uid in active_user_ids:
|
|
filtered.append(item)
|
|
|
|
recommendations["recommendations"] = filtered
|
|
recommendations["total_count"] = len(filtered)
|
|
return success_response(recommendations)
|
|
|
|
@router.post("/projects/{project_id}/tasks/optimize-workload")
|
|
async def optimize_task_workload(
|
|
project_id: str,
|
|
body: WorkloadOptimizeRequest = Body(default_factory=WorkloadOptimizeRequest),
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
_require_manager(current_user)
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
|
|
proposals = await workload_service.build_ai_optimize_preview(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=pid,
|
|
max_moves=body.max_moves,
|
|
unassigned_only=body.mode == "unassigned",
|
|
)
|
|
if not proposals:
|
|
return success_response(
|
|
{
|
|
"updated": 0,
|
|
"message": "no tasks to optimize",
|
|
"mode": body.mode,
|
|
}
|
|
)
|
|
|
|
optimize_result = await workload_service.apply_ai_optimize_plan(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
proposals=proposals,
|
|
)
|
|
updated = int(optimize_result.get("applied_count") or 0)
|
|
affected_users = int(optimize_result.get("affected_users") or 0)
|
|
|
|
await log_operation(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
user_id=int(current_user.id),
|
|
project_id=pid,
|
|
entity_type="task",
|
|
entity_id=str(pid),
|
|
action="optimize_workload",
|
|
after_data={
|
|
"updated": updated,
|
|
"affected_users": affected_users,
|
|
"proposals": len(proposals),
|
|
"mode": body.mode,
|
|
},
|
|
)
|
|
await db.commit()
|
|
return success_response(
|
|
{
|
|
"updated": updated,
|
|
"affected_users": affected_users,
|
|
"proposals": len(proposals),
|
|
"mode": body.mode,
|
|
}
|
|
)
|
|
|