from pydantic import BaseModel, computed_field from typing import Optional, List, Any, Dict from datetime import datetime from decimal import Decimal from enum import Enum class Priority(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" URGENT = "urgent" class CustomValueInput(BaseModel): """Input for setting a custom field value.""" field_id: str value: Optional[Any] = None # Can be string, number, date string, or user id class CustomValueResponse(BaseModel): """Response for a custom field value.""" field_id: str field_name: str field_type: str value: Optional[Any] = None display_value: Optional[str] = None # Formatted for display class TaskBase(BaseModel): title: str description: Optional[str] = None priority: Priority = Priority.MEDIUM original_estimate: Optional[Decimal] = None start_date: Optional[datetime] = None due_date: Optional[datetime] = None class TaskCreate(TaskBase): parent_task_id: Optional[str] = None assignee_id: Optional[str] = None status_id: Optional[str] = None custom_values: Optional[List[CustomValueInput]] = None class TaskUpdate(BaseModel): title: Optional[str] = None description: Optional[str] = None priority: Optional[Priority] = None original_estimate: Optional[Decimal] = None time_spent: Optional[Decimal] = None start_date: Optional[datetime] = None due_date: Optional[datetime] = None position: Optional[int] = None custom_values: Optional[List[CustomValueInput]] = None class TaskStatusUpdate(BaseModel): status_id: str class TaskAssignUpdate(BaseModel): assignee_id: Optional[str] = None class TaskResponse(TaskBase): id: str project_id: str parent_task_id: Optional[str] = None assignee_id: Optional[str] = None status_id: Optional[str] = None time_spent: Decimal blocker_flag: bool position: int created_by: str created_at: datetime updated_at: datetime class Config: from_attributes = True # Alias for original_estimate for frontend compatibility @computed_field @property def time_estimate(self) -> Optional[Decimal]: return self.original_estimate class TaskWithDetails(TaskResponse): assignee_name: Optional[str] = None status_name: Optional[str] = None status_color: Optional[str] = None creator_name: Optional[str] = None subtask_count: int = 0 custom_values: Optional[List[CustomValueResponse]] = None class TaskListResponse(BaseModel): tasks: List[TaskWithDetails] total: int