70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from sqlalchemy import CheckConstraint, ForeignKey, JSON, String, Text
|
|
from sqlalchemy.ext.mutable import MutableList
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.extensions import db
|
|
|
|
|
|
TASK_STATUSES = ("not_started", "in_progress", "done")
|
|
TASK_PRIORITIES = ("urgent", "important", "normal")
|
|
|
|
|
|
class Task(db.Model):
|
|
__table_args__ = (
|
|
CheckConstraint(
|
|
"status IN ('not_started', 'in_progress', 'done')",
|
|
name="ck_task_status",
|
|
),
|
|
CheckConstraint(
|
|
"priority IN ('urgent', 'important', 'normal')",
|
|
name="ck_task_priority",
|
|
),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
title: Mapped[str] = mapped_column(String(200))
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
status: Mapped[str] = mapped_column(String(20), default="not_started")
|
|
priority: Mapped[str] = mapped_column(String(20), default="normal")
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
due_date: Mapped[datetime | None]
|
|
completed_at: Mapped[datetime | None]
|
|
user_id: Mapped[int] = mapped_column(
|
|
ForeignKey("user.id", ondelete="CASCADE"), index=True
|
|
)
|
|
|
|
# Stored now so Phase 3 can add timers and chunking without reshaping CRUD.
|
|
timer_seconds: Mapped[int] = mapped_column(default=0)
|
|
context: Mapped[str | None] = mapped_column(String(100))
|
|
chunk_sessions: Mapped[list[Any]] = mapped_column(
|
|
MutableList.as_mutable(JSON), default=list
|
|
)
|
|
|
|
subtasks: Mapped[list["Subtask"]] = relationship(
|
|
back_populates="task",
|
|
cascade="all, delete-orphan",
|
|
order_by="Subtask.id",
|
|
)
|
|
|
|
@property
|
|
def progress_percentage(self) -> int:
|
|
if not self.subtasks:
|
|
return 100 if self.status == "done" else 0
|
|
completed = sum(subtask.completed for subtask in self.subtasks)
|
|
return round(completed / len(self.subtasks) * 100)
|
|
|
|
|
|
class Subtask(db.Model):
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
title: Mapped[str] = mapped_column(String(100))
|
|
completed: Mapped[bool] = mapped_column(default=False)
|
|
task_id: Mapped[int] = mapped_column(
|
|
ForeignKey("task.id", ondelete="CASCADE"), index=True
|
|
)
|
|
|
|
task: Mapped[Task] = relationship(back_populates="subtasks")
|