PMFlow/backend/app/api/sstc_api/tasks_update_delete.py

178 lines
6.5 KiB
Python

from __future__ import annotations
from .shared import *
from ...services.context_cache_service import context_cache_service
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,
"required_skill_tag_ids": list(task.required_skill_tag_ids or []),
"manual_overrides": task.manual_overrides,
}
old_assignee_id = int(task.assignee_id) if task.assignee_id else None
manual_overrides = dict(task.manual_overrides or {})
if body.title is not None:
task.title = compact_title(body.title, max_len=32)
manual_overrides["title"] = task.title
if body.description is not None:
task.description = body.description
manual_overrides["description"] = task.description
if body.assignee_id is not None:
task.assignee_id = body.assignee_id
manual_overrides["assignee_id"] = task.assignee_id
if body.required_skill_tag_ids is not None:
task.required_skill_tag_ids = body.required_skill_tag_ids
manual_overrides["required_skill_tag_ids"] = list(task.required_skill_tag_ids or [])
if body.priority is not None:
task.priority = body.priority
manual_overrides["priority"] = task.priority
if body.estimated_hours is not None:
task.estimated_hours = body.estimated_hours
manual_overrides["estimated_hours"] = float(task.estimated_hours) if task.estimated_hours is not None else None
if body.due_date is not None:
task.due_date = _parse_due_date(body.due_date)
manual_overrides["due_date"] = task.due_date.isoformat() if task.due_date else None
task.manual_overrides = manual_overrides or 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="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,
"required_skill_tag_ids": list(task.required_skill_tag_ids or []),
"manual_overrides": task.manual_overrides,
},
)
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})