Newer
Older
gnexus-tasks / backend / app / schemas.py
"""Pydantic-схемы API M1."""

from datetime import date, datetime
from typing import Any

from pydantic import BaseModel, ConfigDict, Field

# --- Task ---

DEADLINE_PERIODS = ("day", "week", "month", "year")
RECUR_KINDS = ("interval", "weekdays", "monthly")


class TagOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    name: str


class ProjectOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    name: str
    relevance_status: str
    priority: int | None
    note: str


class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=500)
    description: str = ""
    parent_task_id: int | None = None  # подзадача (M3)


class TaskUpdate(BaseModel):
    """Частичное обновление метаданных задачи (ручная детализация)."""

    title: str | None = Field(None, max_length=500)
    description: str | None = None
    project_id: int | None = None
    parent_task_id: int | None = None
    tag_ids: list[int] | None = None
    priority: int | None = None
    status: str | None = None
    estimated_minutes: int | None = Field(None, ge=1, le=24 * 60)
    actual_minutes: int | None = Field(None, ge=1, le=24 * 60)
    budget_money: int | None = Field(None, ge=0)
    cost_estimate_money: int | None = Field(None, ge=0)
    # Дедлайны: строгий — конкретная дата; нестрогий — период («в течение…»)
    deadline_date: date | None = None
    deadline_period: str | None = None
    # Регулярность (ТЗ 3.5): тип задачи + правило повторения
    task_type: str | None = None
    recur_kind: str | None = Field(None, pattern="^(interval|weekdays|monthly)$")
    recur_interval_days: int | None = Field(None, ge=1, le=365)
    recur_weekdays: str | None = Field(None, pattern=r"^[1-7](,[1-7])*$")  # ISO, пн=1
    recur_day_of_month: int | None = Field(None, ge=1, le=31)


class TaskOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    title: str
    description: str
    task_type: str
    status: str
    detail_state: str
    parent_task_id: int | None
    project: ProjectOut | None
    priority: int | None
    tags: list[TagOut]
    ai_proposal: dict[str, Any] | None
    estimated_minutes: int | None
    actual_minutes: int | None
    budget_money: int | None
    cost_estimate_money: int | None
    deadline_date: date | None
    deadline_period: str | None
    recur_kind: str | None
    recur_interval_days: int | None
    recur_weekdays: str | None
    recur_day_of_month: int | None
    created_at: datetime
    approved_at: datetime | None
    done_at: datetime | None


class ApproveIn(BaseModel):
    """Утверждение детализации; apply_proposal=True — принять предложение LLM."""

    apply_proposal: bool = False


class SuggestIn(BaseModel):
    """Режим «3 варианта»: доступное время в минутах."""

    available_minutes: int = Field(ge=5, le=24 * 60)


# --- Project / Tag ---


class AttachmentOut(BaseModel):
    """Вложение задачи (изображение)."""

    model_config = ConfigDict(from_attributes=True)

    id: int
    task_id: int
    filename: str
    original_name: str
    mime: str
    size: int
    created_at: datetime


class ProjectCreate(BaseModel):
    name: str = Field(min_length=1, max_length=200)
    priority: int | None = None
    note: str = ""


class ProjectUpdate(BaseModel):
    name: str | None = Field(None, max_length=200)
    relevance_status: str | None = None
    priority: int | None = None
    note: str | None = None


class TagCreate(BaseModel):
    name: str = Field(min_length=1, max_length=100)


CURRENCIES = ("UAH", "USD", "EUR", "GBP", "PLN")


class AppSettingsOut(BaseModel):
    currency: str = "UAH"


class AppSettingsUpdate(BaseModel):
    currency: str = Field(pattern="^(UAH|USD|EUR|GBP|PLN)$")