from sqlalchemy import Column, Integer, String, Boolean, DateTime, Enum, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.sql import func import enum from datetime import datetime, timezone from typing import Optional from app.db.base import Base class Priority(str, enum.Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" class Todo(Base): __tablename__ = "todos" id = Column(Integer, primary_key=True, index=True) title = Column(String(200), nullable=False) description = Column(String(500), nullable=True) completed = Column(Boolean, default=False) priority = Column(Enum(Priority), default=Priority.MEDIUM) project_id = Column(Integer, ForeignKey("projects.id"), nullable=True) category_id = Column(Integer, ForeignKey("categories.id"), nullable=True) parent_id = Column(Integer, ForeignKey("todos.id"), nullable=True) due_date = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationship to project project = relationship("Project", back_populates="todos") # Relationship to category category = relationship("Category", back_populates="todos") # Self-referential relationship for subtasks parent = relationship("Todo", remote_side=[id], back_populates="children") children = relationship( "Todo", back_populates="parent", cascade="all, delete-orphan" ) @property def is_subtask(self) -> bool: """Check if this todo is a subtask of another todo.""" return self.parent_id is not None @property def is_overdue(self) -> bool: """Check if this todo is overdue.""" if self.due_date is None or self.completed: return False return datetime.now(timezone.utc) > self.due_date @property def days_until_due(self) -> Optional[int]: """Calculate the number of days until the due date. Returns: int: Number of days until due (negative if overdue) None: If no due date is set """ if self.due_date is None: return None now = datetime.now(timezone.utc) delta = (self.due_date - now).days return delta