Newer
Older
gnexus-tasks / backend / app / models.py
"""Модели данных M1: задачи, проекты, теги.

Дедлайны/повторения/бюджет/вложения добавляются миграциями в следующих вехах
(см. docs/TZ.md, модель данных).
"""

from datetime import UTC, datetime

from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.db import Base


def utcnow() -> datetime:
    return datetime.now(UTC)


class Task(Base):
    __tablename__ = "tasks"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    title: Mapped[str] = mapped_column(String(500))
    description: Mapped[str] = mapped_column(Text, default="")

    # Тип: разовая / регулярная (регулярные — в следующих вехах)
    task_type: Mapped[str] = mapped_column(String(20), default="one_time")

    # Статус выполнения
    status: Mapped[str] = mapped_column(String(20), default="to_do")
    # Плоскость детализации стека: raw (в стеке) / approved (утверждена)
    detail_state: Mapped[str] = mapped_column(String(20), default="raw")
    done: Mapped[bool] = mapped_column(Boolean, default=False)

    parent_task_id: Mapped[int | None] = mapped_column(
        ForeignKey("tasks.id"), nullable=True, default=None
    )
    project_id: Mapped[int | None] = mapped_column(
        ForeignKey("projects.id"), nullable=True, default=None
    )
    priority: Mapped[int | None] = mapped_column(Integer, nullable=True, default=None)

    tags: Mapped[list["Tag"]] = relationship(
        secondary="task_tags", back_populates="tasks", lazy="selectin"
    )
    project: Mapped["Project | None"] = relationship(back_populates="tasks", lazy="joined")

    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
    approved_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True, default=None
    )
    done_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True, default=None
    )


class Project(Base):
    __tablename__ = "projects"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(200), unique=True)
    # Статус актуальности проекта
    relevance_status: Mapped[str] = mapped_column(String(20), default="active")
    priority: Mapped[int | None] = mapped_column(Integer, nullable=True, default=None)
    note: Mapped[str] = mapped_column(Text, default="")  # markdown: ссылки, контекст

    tasks: Mapped[list[Task]] = relationship(back_populates="project")

    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)


class Tag(Base):
    __tablename__ = "tags"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100), unique=True)

    tasks: Mapped[list[Task]] = relationship(
        secondary="task_tags", back_populates="tags", lazy="selectin"
    )


class TaskTag(Base):
    __tablename__ = "task_tags"

    task_id: Mapped[int] = mapped_column(
        ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True
    )
    tag_id: Mapped[int] = mapped_column(
        ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True
    )