File size: 4,805 Bytes
cccf200
 
fa948af
 
cccf200
fa948af
cccf200
fa948af
0644a5c
fa948af
 
 
 
 
 
 
 
 
 
cccf200
 
 
 
 
 
 
fa948af
cccf200
fa948af
 
 
cccf200
 
 
 
 
 
 
fa948af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cccf200
fa948af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0644a5c
 
cccf200
 
 
fa948af
 
cccf200
 
 
 
 
 
fa948af
 
cccf200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa948af
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""
Task model migrated to SQLModel with PostgreSQL support.

Extended for 012-advanced-todo-features with due dates, recurrence, and reminders.
"""
from typing import Optional
from datetime import datetime
from enum import Enum
from sqlmodel import SQLModel, Field, Column
from sqlalchemy import DateTime


class RecurrencePattern(str, Enum):
    """Recurrence pattern enumeration for recurring tasks."""
    DAILY = "daily"
    WEEKLY = "weekly"
    BI_WEEKLY = "bi-weekly"
    MONTHLY = "monthly"
    YEARLY = "yearly"


class Task(SQLModel, table=True):
    """Task entity for todo items."""

    __tablename__ = "tasks"

    # Existing fields from 001-todo-ai-chatbot
    id: Optional[int] = Field(default=None, primary_key=True)
    user_id: int = Field(index=True)  # Removed foreign_key constraint for demo users
    title: str = Field(max_length=200, min_length=1)
    description: str = Field(default="", max_length=1000)
    completed: bool = Field(default=False)
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)
    # Offline sync support
    client_id: Optional[str] = Field(default=None, max_length=100, index=True)
    version: int = Field(default=1)

    # NEW FIELDS for 012-advanced-todo-features (due dates, recurrence, reminders)
    due_date: Optional[datetime] = Field(
        default=None,
        sa_column=Column(DateTime(timezone=True)),
        description="Task deadline in UTC timezone"
    )
    recurrence_pattern: Optional[RecurrencePattern] = Field(
        default=None,
        description="Recurrence type: daily/weekly/bi-weekly/monthly/yearly"
    )
    is_recurring: bool = Field(
        default=False,
        description="Whether task auto-creates next instance on completion"
    )
    reminder_minutes: int = Field(
        default=15,
        ge=0,
        le=1440,  # Max 24 hours
        description="Minutes before due_date to send reminder (0-1440)"
    )
    next_occurrence: Optional[datetime] = Field(
        default=None,
        sa_column=Column(DateTime(timezone=True)),
        description="Auto-calculated next due date for recurring tasks"
    )

    def validate_due_date(self) -> None:
        """
        Validate due date constraints.

        Raises:
            ValueError: If due_date is not timezone-aware
        """
        if self.due_date and self.due_date.tzinfo is None:
            raise ValueError("due_date must be timezone-aware (UTC)")

    def validate_recurrence(self) -> None:
        """
        Validate recurrence configuration.

        Raises:
            ValueError: If recurrence settings are invalid
        """
        if self.is_recurring and not self.recurrence_pattern:
            raise ValueError("is_recurring requires recurrence_pattern to be set")
        if self.is_recurring and not self.due_date:
            raise ValueError("Recurring tasks must have a due_date")
        if self.recurrence_pattern and not self.is_recurring:
            # Auto-set is_recurring if pattern is set
            self.is_recurring = True

    def calculate_next_occurrence(self) -> datetime:
        """
        Calculate next occurrence for recurring tasks.

        Returns:
            Next occurrence datetime in UTC

        Raises:
            ValueError: If not a recurring task or missing due_date
        """
        if not self.is_recurring or not self.due_date or not self.recurrence_pattern:
            raise ValueError("Can only calculate next occurrence for recurring tasks")

        from ..utils.recurrence_calculator import RecurrenceCalculator

        return RecurrenceCalculator.calculate_next_occurrence(
            self.due_date,
            self.recurrence_pattern.value
        )


class TaskCreate(SQLModel):
    """Schema for creating a new task."""

    title: str = Field(max_length=200, min_length=1)
    description: str = Field(default="", max_length=1000)
    client_id: Optional[str] = Field(default=None, max_length=100)


class TaskUpdate(SQLModel):
    """Schema for updating a task."""

    title: Optional[str] = Field(default=None, max_length=200, min_length=1)
    description: Optional[str] = Field(default=None, max_length=1000)
    completed: Optional[bool] = Field(default=None)


class TaskResponse(SQLModel):
    """Schema for task response."""

    id: int
    user_id: int
    title: str
    description: str
    completed: bool
    created_at: datetime
    updated_at: datetime
    client_id: Optional[str] = None
    version: int

    # NEW FIELDS for 012-advanced-todo-features
    due_date: Optional[datetime] = None
    recurrence_pattern: Optional[RecurrencePattern] = None
    is_recurring: bool = False
    reminder_minutes: int = 15
    next_occurrence: Optional[datetime] = None