diff options
Diffstat (limited to 'internal/store/native_tasks.go')
| -rw-r--r-- | internal/store/native_tasks.go | 98 |
1 files changed, 69 insertions, 29 deletions
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go index 955b7ac..5dd3e52 100644 --- a/internal/store/native_tasks.go +++ b/internal/store/native_tasks.go @@ -21,6 +21,16 @@ import ( // completion: the HTTP response was 200, but nothing in the database changed. var ErrNativeTaskNotFound = errors.New("native task not found") +// nativeTaskColumns is the shared SELECT list for every query that scans into +// models.Task via scanNativeTasks. Kept in one place because the list and the +// scan order have to agree exactly, and it had previously been copy-pasted into +// nine separate queries -- adding a column meant editing all nine correctly or +// silently breaking whichever was missed. +const nativeTaskColumns = `id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, + recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, + chain_id, chain_position, chain_unlocked, + bucket_id, bucket_state, bucket_last_active_at, committed_date` + // visibleChainTaskClause is the single source of truth for "which chain tasks // belong on the list." Every task query that hides locked chain work must use // this rather than spelling the predicate out inline -- see @@ -38,10 +48,7 @@ const visibleChainTaskClause = `(t.chain_id = '' OR (t.chain_unlocked = 1 AND EX // GetNativeTasks returns all non-completed native tasks. func (s *Store) GetNativeTasks() ([]models.Task, error) { rows, err := s.db.Query(` - SELECT id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, - recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, - chain_id, chain_position, chain_unlocked, - bucket_id, bucket_state, bucket_last_active_at + SELECT ` + nativeTaskColumns + ` FROM native_tasks WHERE completed = 0 AND abandoned = 0 ORDER BY CASE WHEN due_date IS NULL THEN 1 ELSE 0 END, due_date ASC, priority DESC @@ -59,10 +66,7 @@ func (s *Store) GetNativeTasks() ([]models.Task, error) { // without double-counting against that separate fetch. func (s *Store) GetNativeTasksByDateRange(start, end time.Time) ([]models.Task, error) { rows, err := s.db.Query(` - SELECT id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, - recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, - chain_id, chain_position, chain_unlocked, - bucket_id, bucket_state, bucket_last_active_at + SELECT ` + nativeTaskColumns + ` FROM native_tasks t WHERE completed = 0 AND abandoned = 0 AND due_date IS NOT NULL AND due_date >= ? AND due_date < ? AND `+visibleChainTaskClause+` @@ -83,10 +87,7 @@ func (s *Store) GetNativeTasksByDateRange(start, end time.Time) ([]models.Task, // ComputeDaySection to be marked IsOverdue. func (s *Store) GetOverdueNativeTasks(before time.Time) ([]models.Task, error) { rows, err := s.db.Query(` - SELECT id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, - recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, - chain_id, chain_position, chain_unlocked, - bucket_id, bucket_state, bucket_last_active_at + SELECT ` + nativeTaskColumns + ` FROM native_tasks t WHERE completed = 0 AND abandoned = 0 AND due_date IS NOT NULL AND due_date < ? AND `+visibleChainTaskClause+` @@ -102,13 +103,10 @@ func (s *Store) GetOverdueNativeTasks(before time.Time) ([]models.Task, error) { // GetUndatedNativeTasks returns non-completed native tasks with no due date. func (s *Store) GetUndatedNativeTasks() ([]models.Task, error) { rows, err := s.db.Query(` - SELECT id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, - recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, - chain_id, chain_position, chain_unlocked, - bucket_id, bucket_state, bucket_last_active_at + SELECT ` + nativeTaskColumns + ` FROM native_tasks t WHERE completed = 0 AND abandoned = 0 AND due_date IS NULL - AND `+visibleChainTaskClause+` + AND t.chain_id = '' AND bucket_state != 'dormant' ORDER BY priority DESC, created_at ASC `) @@ -122,10 +120,7 @@ func (s *Store) GetUndatedNativeTasks() ([]models.Task, error) { // GetNativeTaskByID returns a single native task by id, or ErrNativeTaskNotFound. func (s *Store) GetNativeTaskByID(id string) (*models.Task, error) { rows, err := s.db.Query(` - SELECT id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, - recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, - chain_id, chain_position, chain_unlocked, - bucket_id, bucket_state, bucket_last_active_at + SELECT ` + nativeTaskColumns + ` FROM native_tasks WHERE id = ? `, id) @@ -214,11 +209,10 @@ func (s *Store) DeleteNativeTask(id string) error { } if task.ChainUnlocked { - now := config.Now() result, err := tx.Exec(` - UPDATE native_tasks SET chain_unlocked = 1, due_date = ?, updated_at = ? + UPDATE native_tasks SET chain_unlocked = 1, updated_at = ? WHERE chain_id = ? AND chain_position = ? - `, now, now, task.ChainID, task.ChainPosition) + `, config.Now(), task.ChainID, task.ChainPosition) if err != nil { return err } @@ -460,10 +454,7 @@ func (s *Store) SetNextOccurrenceOverride(id string, date time.Time) error { // ignored recurring task doesn't block its successor from appearing. func (s *Store) GetSeriesNeedingNextIteration(now time.Time) ([]models.Task, error) { rows, err := s.db.Query(` - SELECT id, content, description, project_name, project_id, due_date, priority, completed, labels, created_at, - recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes, - chain_id, chain_position, chain_unlocked, - bucket_id, bucket_state, bucket_last_active_at + SELECT ` + nativeTaskColumns + ` FROM native_tasks t1 WHERE recurrence_series_id != '' AND due_date IS NOT NULL AND due_date <= ? @@ -537,17 +528,23 @@ func scanNativeTasks(rows interface { var weekdaysStr string var nextOverrideStr string var bucketLastActiveAt sql.NullTime + var committedDate sql.NullString if err := rows.Scan( &t.ID, &t.Content, &t.Description, &t.ProjectName, &t.ProjectID, &dueDateStr, &t.Priority, &t.Completed, &labelsJSON, &t.CreatedAt, &t.RecurrenceFreq, &t.RecurrenceInterval, &weekdaysStr, &t.RecurrenceSeriesID, &nextOverrideStr, &t.EstimatedMinutes, &t.ChainID, &t.ChainPosition, &t.ChainUnlocked, - &t.BucketID, &t.BucketState, &bucketLastActiveAt, + &t.BucketID, &t.BucketState, &bucketLastActiveAt, &committedDate, ); err != nil { return nil, err } if bucketLastActiveAt.Valid { t.BucketLastActiveAt = &bucketLastActiveAt.Time } + if committedDate.Valid && committedDate.String != "" { + if parsed, err := time.Parse("2006-01-02", committedDate.String); err == nil { + t.CommittedDate = &parsed + } + } if dueDateStr != nil { if parsed, err := time.Parse(time.RFC3339, *dueDateStr); err == nil { t.DueDate = &parsed @@ -598,3 +595,46 @@ func formatWeekdays(weekdays []int) string { } return strings.Join(strs, ",") } + +// SetTaskCommitted marks a task as chosen for a given day. This is the "I'll do +// this today" signal, deliberately separate from due_date, which now means only +// an externally-imposed deadline -- see migration 031. Returns +// ErrNativeTaskNotFound if id doesn't match any row. +func (s *Store) SetTaskCommitted(id string, day time.Time) error { + result, err := s.db.Exec(` + UPDATE native_tasks SET committed_date = ?, updated_at = ? WHERE id = ? + `, day.Format("2006-01-02"), config.Now(), id) + if err != nil { + return err + } + return checkRowsAffected(result) +} + +// ClearTaskCommitted removes a task's commitment, dropping it back to the +// backlog without completing it. +func (s *Store) ClearTaskCommitted(id string) error { + result, err := s.db.Exec(` + UPDATE native_tasks SET committed_date = NULL, updated_at = ? WHERE id = ? + `, config.Now(), id) + if err != nil { + return err + } + return checkRowsAffected(result) +} + +// GetCommittedTasks returns the open tasks committed to a given day. Chain +// steps are deliberately NOT filtered out here: committing is exactly how chain +// work is meant to reach the day's list. +func (s *Store) GetCommittedTasks(day time.Time) ([]models.Task, error) { + rows, err := s.db.Query(` + SELECT ` + nativeTaskColumns + ` + FROM native_tasks t + WHERE completed = 0 AND abandoned = 0 AND committed_date = ? + ORDER BY priority DESC, created_at ASC + `, day.Format("2006-01-02")) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + return scanNativeTasks(rows) +} |
