Files
HarborForge.Backend/app/schemas/schemas.py

374 lines
8.9 KiB
Python

from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime, time
from enum import Enum
class TaskTypeEnum(str, Enum):
ISSUE = "issue"
MAINTENANCE = "maintenance"
RESEARCH = "research"
REVIEW = "review"
STORY = "story"
TEST = "test"
RESOLUTION = "resolution"
# P7.1: 'task' type removed — defect subtype migrated to issue/defect
class TaskStatusEnum(str, Enum):
OPEN = "open"
PENDING = "pending"
UNDERGOING = "undergoing"
COMPLETED = "completed"
CLOSED = "closed"
class TaskPriorityEnum(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
# Task schemas
class TaskBase(BaseModel):
title: str
description: Optional[str] = None
task_type: TaskTypeEnum = TaskTypeEnum.ISSUE
task_subtype: Optional[str] = None
priority: TaskPriorityEnum = TaskPriorityEnum.MEDIUM
tags: Optional[str] = None
estimated_effort: Optional[int] = None
estimated_working_time: Optional[str] = None
class TaskCreate(TaskBase):
project_id: Optional[int] = None
project_code: Optional[str] = None
milestone_id: Optional[int] = None
milestone_code: Optional[str] = None
reporter_id: Optional[int] = None
assignee_id: Optional[int] = None
type: Optional[TaskTypeEnum] = None
# Resolution specific
resolution_summary: Optional[str] = None
positions: Optional[str] = None
pending_matters: Optional[str] = None
class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
task_type: Optional[TaskTypeEnum] = None
type: Optional[TaskTypeEnum] = None
task_subtype: Optional[str] = None
status: Optional[TaskStatusEnum] = None
priority: Optional[TaskPriorityEnum] = None
assignee_id: Optional[int] = None
taken_by: Optional[str] = None
tags: Optional[str] = None
estimated_effort: Optional[int] = None
# Resolution specific
resolution_summary: Optional[str] = None
positions: Optional[str] = None
pending_matters: Optional[str] = None
class TaskResponse(TaskBase):
id: int
status: TaskStatusEnum
task_code: Optional[str] = None
code: Optional[str] = None
type: Optional[str] = None
due_date: Optional[datetime] = None
project_id: int
project_code: Optional[str] = None
milestone_id: int
milestone_code: Optional[str] = None
reporter_id: int
assignee_id: Optional[int] = None
taken_by: Optional[str] = None
created_by_id: Optional[int] = None
estimated_working_time: Optional[time] = None
resolution_summary: Optional[str] = None
positions: Optional[str] = None
pending_matters: Optional[str] = None
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# Comment schemas
class CommentBase(BaseModel):
content: str
class CommentCreate(CommentBase):
task_id: int
author_id: int
class CommentUpdate(BaseModel):
content: Optional[str] = None
class CommentResponse(CommentBase):
id: int
task_id: int
author_id: int
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# Project schemas
class ProjectBase(BaseModel):
name: str
owner_name: Optional[str] = None
description: Optional[str] = None
repo: Optional[str] = None
sub_projects: Optional[list[str]] = None
related_projects: Optional[list[str]] = None
class ProjectCreate(ProjectBase):
owner_id: int
class ProjectUpdate(BaseModel):
description: Optional[str] = None
owner_name: Optional[str] = None
repo: Optional[str] = None
sub_projects: Optional[list[str]] = None
related_projects: Optional[list[str]] = None
class ProjectResponse(BaseModel):
id: int
name: str
owner_name: Optional[str] = None
project_code: Optional[str] = None
description: Optional[str] = None
repo: Optional[str] = None
sub_projects: Optional[list[str]] = None
related_projects: Optional[list[str]] = None
owner_id: int
created_at: datetime
class Config:
from_attributes = True
# User schemas
class UserBase(BaseModel):
username: str
email: str
full_name: Optional[str] = None
class UserCreate(UserBase):
password: Optional[str] = None
role_id: Optional[int] = None
class UserUpdate(BaseModel):
full_name: Optional[str] = None
email: Optional[str] = None
password: Optional[str] = None
role_id: Optional[int] = None
is_active: Optional[bool] = None
class UserResponse(UserBase):
id: int
is_active: bool
is_admin: bool
role_id: Optional[int] = None
role_name: Optional[str] = None
created_at: datetime
class Config:
from_attributes = True
# Project Member schemas
class ProjectMemberBase(BaseModel):
user_id: int
role: str = "dev"
class ProjectMemberCreate(ProjectMemberBase):
pass
class ProjectMemberResponse(BaseModel):
id: int
user_id: int
username: Optional[str] = None
full_name: Optional[str] = None
project_id: int
role: str = "dev"
class Config:
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[MilestoneStatusEnum] = MilestoneStatusEnum.OPEN
due_date: Optional[datetime] = None
planned_release_date: Optional[datetime] = None
depend_on_milestones: Optional[List[str]] = None
depend_on_tasks: Optional[List[int]] = None
class MilestoneCreate(MilestoneBase):
project_id: Optional[int] = None
pass
class MilestoneUpdate(BaseModel):
title: Optional[str] = None
description: 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
depend_on_tasks: Optional[List[int]] = None
class MilestoneResponse(MilestoneBase):
id: int
milestone_code: Optional[str] = None
project_id: int
created_by_id: Optional[int] = None
started_at: Optional[datetime] = None
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# Proposal schemas (renamed from Propose)
class ProposalStatusEnum(str, Enum):
OPEN = "open"
ACCEPTED = "accepted"
REJECTED = "rejected"
class ProposalBase(BaseModel):
title: str
description: Optional[str] = None
class ProposalCreate(ProposalBase):
project_id: Optional[int] = None
class ProposalUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
class ProposalResponse(ProposalBase):
id: int
proposal_code: Optional[str] = None # preferred name
propose_code: Optional[str] = None # backward compat alias (same value)
status: ProposalStatusEnum
project_id: int
created_by_id: Optional[int] = None
created_by_username: Optional[str] = None
feat_task_id: Optional[str] = None # DEPRECATED — will be removed after BE-PR-008
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# ---------------------------------------------------------------------------
# Essential schemas (under Proposal)
# ---------------------------------------------------------------------------
class EssentialTypeEnum(str, Enum):
FEATURE = "feature"
IMPROVEMENT = "improvement"
REFACTOR = "refactor"
class EssentialBase(BaseModel):
title: str
type: EssentialTypeEnum
description: Optional[str] = None
class EssentialCreate(EssentialBase):
"""Create a new Essential under a Proposal.
``proposal_id`` is inferred from the URL path, not the body.
"""
pass
class EssentialUpdate(BaseModel):
title: Optional[str] = None
type: Optional[EssentialTypeEnum] = None
description: Optional[str] = None
class EssentialResponse(EssentialBase):
id: int
essential_code: str
proposal_id: int
created_by_id: Optional[int] = None
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class ProposalDetailResponse(ProposalResponse):
"""Extended Proposal response that embeds its Essential list."""
essentials: List[EssentialResponse] = []
class Config:
from_attributes = True
# Backward-compatible aliases
ProposeStatusEnum = ProposalStatusEnum
ProposeBase = ProposalBase
ProposeCreate = ProposalCreate
ProposeUpdate = ProposalUpdate
ProposeResponse = ProposalResponse
# Paginated response
from typing import Generic, TypeVar
T = TypeVar("T")
class PaginatedResponse(BaseModel, Generic[T]):
items: List[T]
total: int
page: int
page_size: int
total_pages: int