feat(P4.3): wire task depend_on check into pending→open transition via reusable helper
This commit is contained in:
@@ -16,6 +16,7 @@ from app.models.notification import Notification as NotificationModel
|
||||
from app.api.deps import get_current_user_or_apikey
|
||||
from app.api.rbac import check_project_role, check_permission, ensure_can_edit_task
|
||||
from app.services.activity import log_activity
|
||||
from app.services.dependency_check import check_task_deps
|
||||
|
||||
router = APIRouter(tags=["Tasks"])
|
||||
|
||||
@@ -293,7 +294,7 @@ def transition_task(
|
||||
# P5.1: enforce state-machine
|
||||
_check_transition(old_status, new_status)
|
||||
|
||||
# P5.2: pending -> open requires milestone to be undergoing (dependencies checked later)
|
||||
# P5.2: pending -> open requires milestone to be undergoing + task deps satisfied
|
||||
if old_status == "pending" and new_status == "open":
|
||||
milestone = db.query(Milestone).filter(Milestone.id == task.milestone_id).first()
|
||||
if milestone:
|
||||
@@ -303,6 +304,13 @@ def transition_task(
|
||||
status_code=400,
|
||||
detail=f"Cannot open task: milestone is '{ms_status}', must be 'undergoing'",
|
||||
)
|
||||
# P4.3: check task-level depend_on
|
||||
dep_result = check_task_deps(db, task.depend_on)
|
||||
if not dep_result.ok:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot open task: {dep_result.reason}",
|
||||
)
|
||||
|
||||
# P5.3: open -> undergoing requires assignee AND operator must be the assignee
|
||||
if old_status == "open" and new_status == "undergoing":
|
||||
|
||||
@@ -111,3 +111,38 @@ def check_milestone_deps(
|
||||
result.blockers.append(f"Dependent tasks not {required_status}: {incomplete_tasks}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def check_task_deps(
|
||||
db: Session,
|
||||
depend_on: str | None,
|
||||
*,
|
||||
required_status: str = "completed",
|
||||
) -> DepCheckResult:
|
||||
"""Check whether a task's depend_on tasks are all satisfied.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
db:
|
||||
Active DB session.
|
||||
depend_on:
|
||||
JSON-encoded list of task IDs (from the task's ``depend_on`` field).
|
||||
required_status:
|
||||
The status dependees must have reached (default ``"completed"``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
DepCheckResult with ``ok=True`` if all deps satisfied, else ``ok=False``
|
||||
with human-readable ``blockers``.
|
||||
"""
|
||||
result = DepCheckResult()
|
||||
task_ids = _parse_json_ids(depend_on)
|
||||
if task_ids:
|
||||
dep_tasks: Sequence[Task] = (
|
||||
db.query(Task).filter(Task.id.in_(task_ids)).all()
|
||||
)
|
||||
incomplete = [t.id for t in dep_tasks if _task_status(t) != required_status]
|
||||
if incomplete:
|
||||
result.ok = False
|
||||
result.blockers.append(f"Dependent tasks not {required_status}: {incomplete}")
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user