feat(P3.1): milestone action endpoints — freeze/start/close + auto-complete hook

- New milestone_actions router with POST freeze/start/close endpoints
- freeze: validates exactly 1 release maintenance task exists
- start: validates all milestone/task dependencies completed, records started_at
- close: allows from open/freeze/undergoing with reason
- try_auto_complete_milestone helper: auto-completes milestone when sole release task finishes
- Wired auto-complete into task transition and update endpoints
- Added freeze enforcement: no new feature story tasks after freeze
- Added started_at to milestone serializer
- All actions write activity logs
This commit is contained in:
zhi
2026-03-17 04:03:05 +00:00
parent 75ccbcb362
commit 7d8c448cb8
4 changed files with 336 additions and 2 deletions

View File

@@ -31,6 +31,7 @@ def _serialize_milestone(milestone):
"depend_on_tasks": json.loads(milestone.depend_on_tasks) if milestone.depend_on_tasks else [],
"project_id": milestone.project_id,
"created_by_id": milestone.created_by_id,
"started_at": milestone.started_at,
"created_at": milestone.created_at,
"updated_at": milestone.updated_at,
}
@@ -111,8 +112,14 @@ def create_milestone_task(project_id: int, milestone_id: int, task_data: schemas
if not milestone:
raise HTTPException(status_code=404, detail="Milestone not found")
if milestone.status and hasattr(milestone.status, 'value') and milestone.status.value == "undergoing":
raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is undergoing")
ms_status = milestone.status.value if hasattr(milestone.status, 'value') else milestone.status
if ms_status in ("undergoing", "completed", "closed"):
raise HTTPException(status_code=400, detail=f"Cannot add items to a milestone that is '{ms_status}'")
# P3.6 / §5: freeze prevents adding new feature story tasks
task_type = task_data.model_dump(exclude_unset=True).get("task_type", "")
task_subtype = task_data.model_dump(exclude_unset=True).get("task_subtype", "")
if ms_status == "freeze" and task_type == "story" and task_subtype == "feature":
raise HTTPException(status_code=400, detail="Cannot add feature story tasks after milestone is frozen")
# Generate task_code
milestone_code = milestone.milestone_code or f"m{milestone.id}"