from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON, Boolean, Table from sqlalchemy.orm import relationship from datetime import datetime from .database import Base # Default statuses for new projects DEFAULT_STATUSES = ["backlog", "in_progress", "on_hold", "done"] # Association table for task blocker relationships (many-to-many) task_blockers = Table( "task_blockers", Base.metadata, Column("task_id", Integer, ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True), Column("blocked_by_id", Integer, ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True), ) class Project(Base): __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) name = Column(String(255), nullable=False) description = Column(Text, nullable=True) statuses = Column(JSON, nullable=False, default=DEFAULT_STATUSES) is_archived = Column(Boolean, default=False, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) tasks = relationship("Task", back_populates="project", cascade="all, delete-orphan") class Task(Base): __tablename__ = "tasks" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id"), nullable=False) parent_task_id = Column(Integer, ForeignKey("tasks.id"), nullable=True) title = Column(String(500), nullable=False) description = Column(Text, nullable=True) status = Column(String(50), default="backlog", nullable=False) sort_order = Column(Integer, default=0) estimated_minutes = Column(Integer, nullable=True) tags = Column(JSON, nullable=True) flag_color = Column(String(50), nullable=True) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) project = relationship("Project", back_populates="tasks") parent = relationship("Task", remote_side=[id], backref="subtasks") # blockers: tasks that must be done before this task can start # blocking: tasks that this task is holding up blockers = relationship( "Task", secondary=task_blockers, primaryjoin=lambda: Task.id == task_blockers.c.task_id, secondaryjoin=lambda: Task.id == task_blockers.c.blocked_by_id, backref="blocking", )