feat(P8.3): milestone preflight endpoint for freeze/start button pre-condition checks
This commit is contained in:
@@ -53,6 +53,90 @@ class CloseBody(BaseModel):
|
|||||||
reason: Optional[str] = None
|
reason: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# GET /preflight — lightweight pre-condition check for UI button states
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@router.get("/preflight", status_code=200)
|
||||||
|
def preflight_milestone_actions(
|
||||||
|
project_id: int,
|
||||||
|
milestone_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: models.User = Depends(get_current_user_or_apikey),
|
||||||
|
):
|
||||||
|
"""Return pre-condition check results for freeze / start actions.
|
||||||
|
|
||||||
|
The frontend uses this to decide whether to *disable* buttons and what
|
||||||
|
hint text to show. This endpoint never mutates data.
|
||||||
|
"""
|
||||||
|
check_project_role(db, current_user.id, project_id, min_role="viewer")
|
||||||
|
ms = _get_milestone_or_404(db, project_id, milestone_id)
|
||||||
|
ms_status = _ms_status_value(ms)
|
||||||
|
|
||||||
|
result: dict = {"status": ms_status, "freeze": None, "start": None}
|
||||||
|
|
||||||
|
# --- freeze pre-check (only meaningful when status == open) ---
|
||||||
|
if ms_status == "open":
|
||||||
|
release_tasks = (
|
||||||
|
db.query(Task)
|
||||||
|
.filter(
|
||||||
|
Task.milestone_id == milestone_id,
|
||||||
|
Task.task_type == "maintenance",
|
||||||
|
Task.task_subtype == "release",
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
if len(release_tasks) == 0:
|
||||||
|
result["freeze"] = {
|
||||||
|
"allowed": False,
|
||||||
|
"reason": "No maintenance/release task found. Create one before freezing.",
|
||||||
|
}
|
||||||
|
elif len(release_tasks) > 1:
|
||||||
|
result["freeze"] = {
|
||||||
|
"allowed": False,
|
||||||
|
"reason": f"Found {len(release_tasks)} maintenance/release tasks — expected exactly 1.",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
result["freeze"] = {"allowed": True, "reason": None}
|
||||||
|
|
||||||
|
# --- start pre-check (only meaningful when status == freeze) ---
|
||||||
|
if ms_status == "freeze":
|
||||||
|
blockers: list[str] = []
|
||||||
|
|
||||||
|
# milestone dependencies
|
||||||
|
dep_ms_ids = []
|
||||||
|
if ms.depend_on_milestones:
|
||||||
|
try:
|
||||||
|
dep_ms_ids = json.loads(ms.depend_on_milestones)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
dep_ms_ids = []
|
||||||
|
if dep_ms_ids:
|
||||||
|
dep_milestones = db.query(Milestone).filter(Milestone.id.in_(dep_ms_ids)).all()
|
||||||
|
incomplete = [m.id for m in dep_milestones if _ms_status_value(m) != "completed"]
|
||||||
|
if incomplete:
|
||||||
|
blockers.append(f"Dependent milestones not completed: {incomplete}")
|
||||||
|
|
||||||
|
# task dependencies
|
||||||
|
dep_task_ids = []
|
||||||
|
if ms.depend_on_tasks:
|
||||||
|
try:
|
||||||
|
dep_task_ids = json.loads(ms.depend_on_tasks)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
dep_task_ids = []
|
||||||
|
if dep_task_ids:
|
||||||
|
dep_tasks = db.query(Task).filter(Task.id.in_(dep_task_ids)).all()
|
||||||
|
incomplete_tasks = [t.id for t in dep_tasks if (t.status.value if hasattr(t.status, "value") else t.status) != "completed"]
|
||||||
|
if incomplete_tasks:
|
||||||
|
blockers.append(f"Dependent tasks not completed: {incomplete_tasks}")
|
||||||
|
|
||||||
|
if blockers:
|
||||||
|
result["start"] = {"allowed": False, "reason": "; ".join(blockers)}
|
||||||
|
else:
|
||||||
|
result["start"] = {"allowed": True, "reason": None}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# POST /freeze
|
# POST /freeze
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user