from pydantic import BaseModel, Field, computed_field, model_validator from typing import Optional from datetime import datetime, date from decimal import Decimal from enum import Enum class SecurityLevel(str, Enum): PUBLIC = "public" DEPARTMENT = "department" CONFIDENTIAL = "confidential" class ProjectBase(BaseModel): title: str = Field(..., min_length=1, max_length=500) description: Optional[str] = Field(None, max_length=10000) budget: Optional[Decimal] = Field(None, ge=0, le=99999999999) start_date: Optional[date] = None end_date: Optional[date] = None security_level: SecurityLevel = SecurityLevel.DEPARTMENT @model_validator(mode="before") @classmethod def apply_name_alias(cls, values): if isinstance(values, dict) and not values.get("title") and values.get("name"): values["title"] = values["name"] return values @computed_field @property def name(self) -> str: return self.title class ProjectCreate(ProjectBase): department_id: Optional[str] = None template_id: Optional[str] = None class ProjectUpdate(BaseModel): title: Optional[str] = Field(None, min_length=1, max_length=500) description: Optional[str] = Field(None, max_length=10000) budget: Optional[Decimal] = Field(None, ge=0, le=99999999999) start_date: Optional[date] = None end_date: Optional[date] = None security_level: Optional[SecurityLevel] = None status: Optional[str] = Field(None, max_length=50) department_id: Optional[str] = None @model_validator(mode="before") @classmethod def apply_name_alias(cls, values): if isinstance(values, dict) and not values.get("title") and values.get("name"): values["title"] = values["name"] return values class ProjectResponse(ProjectBase): id: str space_id: str owner_id: str status: str department_id: Optional[str] = None created_at: datetime updated_at: datetime class Config: from_attributes = True class ProjectWithDetails(ProjectResponse): owner_name: Optional[str] = None space_name: Optional[str] = None department_name: Optional[str] = None task_count: int = 0