from __future__ import annotations from .shared import * from ...services.sstc.entity_lookup import _ensure_project, _ensure_task, _parse_due_date from ...services.sstc.governance import _require_manager from ...services.sstc.signal_core import _parse_node_id, _parse_project_id, _sync_pmflow_rag_async router = APIRouter(tags=["sstc"]) class TaskUpdateRequest(BaseModel): title: Optional[str] = None description: Optional[str] = None assignee_id: Optional[int] = None required_skill_tag_ids: Optional[list[int]] = None priority: Optional[str] = None estimated_hours: Optional[float] = None due_date: Optional[str] = None @router.patch("/projects/{project_id}/tasks/{task_id}") async def patch_task( project_id: str, task_id: str, body: TaskUpdateRequest, 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) 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) before = { "title": task.title, "description": task.description, "assignee_id": task.assignee_id, "priority": task.priority, "estimated_hours": float(task.estimated_hours) if task.estimated_hours is not None else None, "due_date": task.due_date.isoformat() if task.due_date else None, } old_assignee_id = int(task.assignee_id) if task.assignee_id else None if body.title is not None: task.title = compact_title(body.title, max_len=32) if body.description is not None: task.description = body.description if body.assignee_id is not None: task.assignee_id = body.assignee_id if body.required_skill_tag_ids is not None: task.required_skill_tag_ids = body.required_skill_tag_ids if body.priority is not None: task.priority = body.priority if body.estimated_hours is not None: task.estimated_hours = body.estimated_hours if body.due_date is not None: task.due_date = _parse_due_date(body.due_date) await log_operation( db, tenant_id=tenant_id, user_id=int(current_user.id), project_id=pid, entity_type="task", entity_id=str(tid), action="update", before_data=before, after_data={ "title": task.title, "description": task.description, "assignee_id": task.assignee_id, "priority": task.priority, "estimated_hours": float(task.estimated_hours) if task.estimated_hours is not None else None, "due_date": task.due_date.isoformat() if task.due_date else None, }, ) touched_user_ids: set[int] = set() if old_assignee_id: touched_user_ids.add(old_assignee_id) if task.assignee_id: touched_user_ids.add(int(task.assignee_id)) if touched_user_ids: for uid in touched_user_ids: await workload_service.refresh_user_assigned_hours_from_tasks( db, tenant_id=tenant_id, user_id=uid, ) else: await db.commit() await context_cache_service.invalidate_system_context(tenant_id=tenant_id) for uid in touched_user_ids: await context_cache_service.invalidate_user_context(tenant_id=tenant_id, user_id=uid) _sync_pmflow_rag_async( tenant_id=tenant_id, record_type="task", record_id=int(task.id), project_id=pid, title=task.title, content=task.description or "", ) return success_response({"id": str(task.id), "status": task.status}) @router.delete("/projects/{project_id}/tasks/{task_id}") async def delete_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 _require_manager(current_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) assignee_id = int(task.assignee_id) if task.assignee_id else None await log_operation( db, tenant_id=tenant_id, user_id=int(current_user.id), project_id=pid, entity_type="task", entity_id=str(tid), action="delete", before_data={ "assignee_id": assignee_id, "estimated_hours": float(task.estimated_hours) if task.estimated_hours else None, "status": task.status, }, ) await db.execute( delete(Task).where( and_( Task.id == tid, Task.project_id == pid, Task.tenant_id == tenant_id, ) ) ) if assignee_id: await workload_service.refresh_user_assigned_hours_from_tasks( db, tenant_id=tenant_id, user_id=assignee_id, ) else: await db.commit() await context_cache_service.invalidate_system_context(tenant_id=tenant_id) if assignee_id: await context_cache_service.invalidate_user_context(tenant_id=tenant_id, user_id=assignee_id) return success_response({"id": str(tid), "deleted": True})