From 9e22c97ae8ab6b68e2619241eeb8a322bde871c8 Mon Sep 17 00:00:00 2001 From: zhi Date: Tue, 17 Mar 2026 00:04:29 +0000 Subject: [PATCH] refactor: update milestone/task status enums to new state machine values Milestone: open/freeze/undergoing/completed/closed (was open/pending/deferred/progressing/closed) Task: open/pending/undergoing/completed/closed (was open/pending/progressing/closed) - Add MilestoneStatusEnum to schemas with typed validation - Add started_at field to Milestone model - Update all router/CLI references from progressing->undergoing - Add completed status handling in task transition logic --- app/api/routers/milestones.py | 4 ++-- app/api/routers/misc.py | 12 ++++++------ app/api/routers/tasks.py | 8 ++++---- app/models/milestone.py | 7 ++++--- app/models/models.py | 3 ++- app/models/task.py | 3 ++- app/schemas/schemas.py | 16 +++++++++++++--- cli.py | 8 +++++--- 8 files changed, 38 insertions(+), 23 deletions(-) diff --git a/app/api/routers/milestones.py b/app/api/routers/milestones.py index 7dfb71b..18670b9 100644 --- a/app/api/routers/milestones.py +++ b/app/api/routers/milestones.py @@ -111,8 +111,8 @@ 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 == "progressing": - raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is in_progress") + 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") # Generate task_code milestone_code = milestone.milestone_code or f"m{milestone.id}" diff --git a/app/api/routers/misc.py b/app/api/routers/misc.py index f6e95ce..f9193f3 100644 --- a/app/api/routers/misc.py +++ b/app/api/routers/misc.py @@ -425,8 +425,8 @@ def create_milestone_task(project_code: str, milestone_id: int, task_data: dict, if not ms: raise HTTPException(status_code=404, detail="Milestone not found") - if ms.status and hasattr(ms.status, "value") and ms.status.value == "progressing": - raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is in_progress") + if ms.status and hasattr(ms.status, "value") and ms.status.value == "undergoing": + raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is undergoing") milestone_code = ms.milestone_code or f"m{ms.id}" max_task = db.query(Task).filter(Task.milestone_id == ms.id).order_by(Task.id.desc()).first() @@ -504,8 +504,8 @@ def create_support(project_code: str, milestone_id: int, support_data: dict, db: if not ms: raise HTTPException(status_code=404, detail="Milestone not found") - if ms.status and hasattr(ms.status, "value") and ms.status.value == "progressing": - raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is in_progress") + if ms.status and hasattr(ms.status, "value") and ms.status.value == "undergoing": + raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is undergoing") milestone_code = ms.milestone_code or f"m{ms.id}" max_support = db.query(Support).filter(Support.milestone_id == milestone_id).order_by(Support.id.desc()).first() @@ -563,8 +563,8 @@ def create_meeting(project_code: str, milestone_id: int, meeting_data: dict, db: if not ms: raise HTTPException(status_code=404, detail="Milestone not found") - if ms.status and hasattr(ms.status, "value") and ms.status.value == "progressing": - raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is in_progress") + if ms.status and hasattr(ms.status, "value") and ms.status.value == "undergoing": + raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is undergoing") milestone_code = ms.milestone_code or f"m{ms.id}" max_meeting = db.query(Meeting).filter(Meeting.milestone_id == milestone_id).order_by(Meeting.id.desc()).first() diff --git a/app/api/routers/tasks.py b/app/api/routers/tasks.py index 78405ae..28940e2 100644 --- a/app/api/routers/tasks.py +++ b/app/api/routers/tasks.py @@ -167,9 +167,9 @@ def update_task(task_id: int, task_update: schemas.TaskUpdate, db: Session = Dep update_data = task_update.model_dump(exclude_unset=True) if "status" in update_data: new_status = update_data["status"] - if new_status == "progressing" and not task.started_on: + if new_status == "undergoing" and not task.started_on: task.started_on = datetime.utcnow() - if new_status == "closed" and not task.finished_on: + if new_status in ("closed", "completed") and not task.finished_on: task.finished_on = datetime.utcnow() for field, value in update_data.items(): @@ -202,9 +202,9 @@ def transition_task(task_id: int, new_status: str, bg: BackgroundTasks, db: Sess if not task: raise HTTPException(status_code=404, detail="Task not found") old_status = task.status.value if hasattr(task.status, 'value') else task.status - if new_status == "progressing" and not task.started_on: + if new_status == "undergoing" and not task.started_on: task.started_on = datetime.utcnow() - if new_status == "closed" and not task.finished_on: + if new_status in ("closed", "completed") and not task.finished_on: task.finished_on = datetime.utcnow() task.status = new_status db.commit() diff --git a/app/models/milestone.py b/app/models/milestone.py index c7c290c..486e0ad 100644 --- a/app/models/milestone.py +++ b/app/models/milestone.py @@ -6,9 +6,9 @@ import enum class MilestoneStatus(str, enum.Enum): OPEN = "open" - PENDING = "pending" - DEFERRED = "deferred" - PROGRESSING = "progressing" + FREEZE = "freeze" + UNDERGOING = "undergoing" + COMPLETED = "completed" CLOSED = "closed" class Milestone(Base): @@ -25,6 +25,7 @@ class Milestone(Base): depend_on_tasks = Column(Text, nullable=True) project_id = Column(Integer, ForeignKey("projects.id"), nullable=False) created_by_id = Column(Integer, ForeignKey("users.id"), nullable=True) + started_at = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) diff --git a/app/models/models.py b/app/models/models.py index 7db8e07..5defa51 100644 --- a/app/models/models.py +++ b/app/models/models.py @@ -21,7 +21,8 @@ class TaskType(str, enum.Enum): class TaskStatus(str, enum.Enum): OPEN = "open" PENDING = "pending" - PROGRESSING = "progressing" + UNDERGOING = "undergoing" + COMPLETED = "completed" CLOSED = "closed" diff --git a/app/models/task.py b/app/models/task.py index 03ed19a..0e6704f 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -7,7 +7,8 @@ import enum class TaskStatus(str, enum.Enum): OPEN = "open" PENDING = "pending" - PROGRESSING = "progressing" + UNDERGOING = "undergoing" + COMPLETED = "completed" CLOSED = "closed" class TaskPriority(str, enum.Enum): diff --git a/app/schemas/schemas.py b/app/schemas/schemas.py index b9e7ce9..071574b 100644 --- a/app/schemas/schemas.py +++ b/app/schemas/schemas.py @@ -18,7 +18,8 @@ class TaskTypeEnum(str, Enum): class TaskStatusEnum(str, Enum): OPEN = "open" PENDING = "pending" - PROGRESSING = "progressing" + UNDERGOING = "undergoing" + COMPLETED = "completed" CLOSED = "closed" @@ -193,11 +194,19 @@ class ProjectMemberResponse(BaseModel): from_attributes = True +class MilestoneStatusEnum(str, Enum): + OPEN = "open" + FREEZE = "freeze" + UNDERGOING = "undergoing" + COMPLETED = "completed" + CLOSED = "closed" + + # Milestone schemas class MilestoneBase(BaseModel): title: str description: Optional[str] = None - status: Optional[str] = "open" + status: Optional[MilestoneStatusEnum] = MilestoneStatusEnum.OPEN due_date: Optional[datetime] = None planned_release_date: Optional[datetime] = None depend_on_milestones: Optional[List[str]] = None @@ -212,7 +221,7 @@ class MilestoneCreate(MilestoneBase): class MilestoneUpdate(BaseModel): title: Optional[str] = None description: Optional[str] = None - status: Optional[str] = None + status: Optional[MilestoneStatusEnum] = None due_date: Optional[datetime] = None planned_release_date: Optional[datetime] = None depend_on_milestones: Optional[List[str]] = None @@ -223,6 +232,7 @@ class MilestoneResponse(MilestoneBase): id: int project_id: int created_by_id: Optional[int] = None + started_at: Optional[datetime] = None created_at: datetime updated_at: Optional[datetime] = None diff --git a/cli.py b/cli.py index bbbe65e..5247888 100755 --- a/cli.py +++ b/cli.py @@ -16,7 +16,9 @@ TOKEN = os.environ.get("HARBORFORGE_TOKEN", "") STATUS_ICON = { "open": "🟢", "pending": "🟡", - "progressing": "🔵", + "freeze": "🧊", + "undergoing": "🔵", + "completed": "✅", "closed": "⚫", } TYPE_ICON = { @@ -241,7 +243,7 @@ def main(): p_tasks = sub.add_parser("tasks", aliases=["issues"], help="List tasks") p_tasks.add_argument("--project", "-p", type=int) p_tasks.add_argument("--type", "-t", choices=["task", "story", "test", "resolution", "issue", "maintenance", "research", "review"]) - p_tasks.add_argument("--status", "-s", choices=["open", "pending", "progressing", "closed"]) + p_tasks.add_argument("--status", "-s", choices=["open", "pending", "undergoing", "completed", "closed"]) p_create = sub.add_parser("create-task", aliases=["create-issue"], help="Create a task") p_create.add_argument("title") @@ -268,7 +270,7 @@ def main(): p_trans = sub.add_parser("transition", help="Transition task status") p_trans.add_argument("task_id", type=int) - p_trans.add_argument("status", choices=["open", "pending", "progressing", "closed"]) + p_trans.add_argument("status", choices=["open", "pending", "undergoing", "completed", "closed"]) p_stats = sub.add_parser("stats", help="Dashboard stats") p_stats.add_argument("--project", "-p", type=int)