home · contact · privacy
For date range inputs, allow "yesterday" and "tomorrow" next to "today".
[plomtask] / plomtask / todos.py
index a7af99faa3a298e27548e7a7234699c1cf8038fc..46a353d91456a13b7968ba6718e02f1ea70e81a8 100644 (file)
@@ -9,6 +9,7 @@ from plomtask.versioned_attributes import VersionedAttribute
 from plomtask.conditions import Condition, ConditionsRelations
 from plomtask.exceptions import (NotFoundException, BadFormatException,
                                  HandledException)
+from plomtask.dating import valid_date
 
 
 @dataclass
@@ -31,6 +32,7 @@ class Todo(BaseModel[int], ConditionsRelations):
                          ('todo_disables', 'todo', 'disables'),
                          ('todo_children', 'parent', 'children'),
                          ('todo_children', 'child', 'parents')]
+    to_search = ['comment']
 
     # pylint: disable=too-many-arguments
     def __init__(self, id_: int | None,
@@ -45,7 +47,7 @@ class Todo(BaseModel[int], ConditionsRelations):
             raise NotFoundException('Process of Todo without ID (not saved?)')
         self.process = process
         self._is_done = is_done
-        self.date = date
+        self.date = valid_date(date)
         self.comment = comment
         self.effort = effort
         self.children: list[Todo] = []
@@ -58,6 +60,34 @@ class Todo(BaseModel[int], ConditionsRelations):
             self.enables = self.process.enables[:]
             self.disables = self.process.disables[:]
 
+    @classmethod
+    def by_date_range(cls, db_conn: DatabaseConnection,
+                      date_range: tuple[str, str] = ('', '')) -> list[Todo]:
+        """Collect Todos of Days within date_range."""
+        todos, _, _ = cls.by_date_range_with_limits(db_conn, date_range)
+        return todos
+
+    @classmethod
+    def create_with_children(cls, db_conn: DatabaseConnection, date: str,
+                             process_ids: list[int]) -> list[Todo]:
+        """Create Todos of process_ids for date, ensure children."""
+        new_todos = []
+        for process_id in process_ids:
+            process = Process.by_id(db_conn, process_id)
+            todo = Todo(None, process, False, date)
+            todo.save(db_conn)
+            new_todos += [todo]
+        nothing_to_adopt = False
+        while not nothing_to_adopt:
+            nothing_to_adopt = True
+            existing_todos = Todo.by_date(db_conn, date)
+            for todo in new_todos:
+                if todo.adopt_from(existing_todos):
+                    nothing_to_adopt = False
+                todo.make_missing_children(db_conn)
+                todo.save(db_conn)
+        return new_todos
+
     @classmethod
     def from_table_row(cls, db_conn: DatabaseConnection,
                        row: Row | list[Any]) -> Todo:
@@ -86,13 +116,16 @@ class Todo(BaseModel[int], ConditionsRelations):
                 target += [Condition.by_id(db_conn, cond_id)]
         return todo
 
+    @classmethod
+    def by_process_id(cls, db_conn: DatabaseConnection,
+                      process_id: int | None) -> list[Todo]:
+        """Collect all Todos of Process of process_id."""
+        return [t for t in cls.all(db_conn) if t.process.id_ == process_id]
+
     @classmethod
     def by_date(cls, db_conn: DatabaseConnection, date: str) -> list[Todo]:
         """Collect all Todos for Day of date."""
-        todos = []
-        for id_ in db_conn.column_where('todos', 'id', 'day', date):
-            todos += [cls.by_id(db_conn, id_)]
-        return todos
+        return cls.by_date_range(db_conn, (date, date))
 
     @property
     def is_doable(self) -> bool:
@@ -154,6 +187,20 @@ class Todo(BaseModel[int], ConditionsRelations):
         """Shortcut to .process.title."""
         return self.process.title
 
+    @property
+    def title_then(self) -> str:
+        """Shortcut to .process.title.at(self.date)"""
+        title_then = self.process.title.at(self.date)
+        assert isinstance(title_then, str)
+        return title_then
+
+    @property
+    def effort_then(self) -> float:
+        """Shortcut to .process.effort.at(self.date)"""
+        effort_then = self.process.effort.at(self.date)
+        assert isinstance(effort_then, float)
+        return effort_then
+
     def adopt_from(self, todos: list[Todo]) -> bool:
         """As far as possible, fill unsatisfied dependencies from todos."""
         adopted = False
@@ -167,10 +214,9 @@ class Todo(BaseModel[int], ConditionsRelations):
 
     def make_missing_children(self, db_conn: DatabaseConnection) -> None:
         """Fill unsatisfied dependencies with new Todos."""
-        for process_id in self.unsatisfied_dependencies:
-            process = Process.by_id(db_conn, process_id)
-            todo = self.__class__(None, process, False, self.date)
-            todo.save(db_conn)
+        new_todos = self.__class__.create_with_children(
+                db_conn, self.date, self.unsatisfied_dependencies)
+        for todo in new_todos:
             self.add_child(todo)
 
     def get_step_tree(self, seen_todos: set[int]) -> TodoNode: