Remove remaining status_id references after in_progress migration

- Remove Preload("Status") from worker handler and repositories
- Update seeds to use in_progress boolean instead of status_id
- Remove task_taskstatus table creation from lookup seeds
- Update documentation to reflect in_progress boolean pattern

Fixes notification worker error:
"Status: unsupported relations for schema Task"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-08 22:43:53 -06:00
parent c5b0225422
commit 12eac24632
8 changed files with 89 additions and 111 deletions

View File

@@ -82,14 +82,14 @@ if task.NextDueDate == nil && len(task.Completions) > 0 {
**Handler**: `InProgressHandler`
**Condition**: `task.Status != nil && task.Status.Name == "In Progress"`
**Condition**: `task.InProgress == true`
**Result**: `in_progress_tasks`
Tasks with the "In Progress" status are grouped together regardless of their due date. This allows users to see what's actively being worked on.
Tasks marked as "In Progress" are grouped together regardless of their due date. This allows users to see what's actively being worked on.
```go
if task.Status != nil && task.Status.Name == "In Progress" {
if task.InProgress {
return "in_progress_tasks"
}
```
@@ -188,15 +188,14 @@ return "upcoming_tasks"
This is by design - "In Progress" indicates active work, so the task should be visible there. However, this means:
1. If a recurring task is marked "In Progress" and then completed
2. The status MUST be reset to "Pending" after completion
2. The `in_progress` flag MUST be reset to `false` after completion
3. Otherwise, the task stays in "In Progress" instead of moving to "Upcoming"
This is handled automatically in `TaskService.CreateCompletion()`:
```go
if isRecurringTask {
pendingStatusID := uint(1)
task.StatusID = &pendingStatusID
task.InProgress = false
}
```
@@ -246,8 +245,8 @@ Each column has associated metadata for UI rendering:
import "github.com/treytartt/casera-api/internal/task/categorization"
task := &models.Task{
DueDate: time.Now().AddDate(0, 0, 15), // 15 days from now
Status: &models.TaskStatus{Name: "Pending"},
DueDate: time.Now().AddDate(0, 0, 15), // 15 days from now
InProgress: false,
}
column := categorization.DetermineKanbanColumn(task, 30)
@@ -291,14 +290,14 @@ Key test scenarios:
1. **Check `IsCancelled`**: Cancelled takes highest priority
2. **Check `NextDueDate`**: For recurring tasks, this determines placement
3. **Check `Status`**: "In Progress" overrides date-based categorization
3. **Check `InProgress`**: `true` overrides date-based categorization
4. **Check `Completions`**: Empty + nil NextDueDate = upcoming, not completed
### Recurring task not moving to Upcoming after completion?
Verify that `CreateCompletion` is:
1. Setting `NextDueDate` correctly
2. Resetting `StatusID` to Pending (1)
2. Resetting `InProgress` to `false`
### Task showing overdue but due date looks correct?

View File

@@ -103,12 +103,12 @@ if len(task.Completions) > 0 {
### 3. In Progress
```go
if task.Status != nil && task.Status.Name == "In Progress" {
if task.InProgress {
inProgress = append(inProgress, task)
continue
}
```
- **Condition**: Task's status name is exactly `"In Progress"`
- **Condition**: Task's `in_progress` boolean is `true`
- **Button Types**: `edit`, `complete`, `cancel`
- **Rationale**: In-progress tasks can be edited, marked complete, or cancelled if work is abandoned

View File

@@ -29,7 +29,7 @@ predicates.IsCompleted(task) // NextDueDate == nil && len(Completions) > 0
predicates.IsActive(task) // !IsCancelled && !IsArchived
predicates.IsCancelled(task) // IsCancelled == true
predicates.IsArchived(task) // IsArchived == true
predicates.IsInProgress(task) // Status.Name == "In Progress"
predicates.IsInProgress(task) // InProgress == true
// Date calculations
predicates.EffectiveDate(task) // NextDueDate ?? DueDate
@@ -105,7 +105,7 @@ These rules are defined in `predicates/predicates.go` and enforced everywhere:
|---------|------------|----------------|
| **Completed** | `NextDueDate == nil && len(Completions) > 0` | `next_due_date IS NULL AND EXISTS (SELECT 1 FROM task_taskcompletion WHERE task_id = ?)` |
| **Active** | `!IsCancelled && !IsArchived` | `is_cancelled = false AND is_archived = false` |
| **In Progress** | `Status.Name == "In Progress"` | `JOIN task_taskstatus WHERE name = 'In Progress'` |
| **In Progress** | `InProgress == true` | `in_progress = true` |
| **Effective Date** | `NextDueDate ?? DueDate` | `COALESCE(next_due_date, due_date)` |
| **Overdue** | `Active && !Completed && EffectiveDate < now` | Active + NotCompleted + `COALESCE(...) < ?` |
| **Due Soon** | `Active && !Completed && now <= EffectiveDate < threshold` | Active + NotCompleted + `COALESCE(...) >= ? AND COALESCE(...) < ?` |
@@ -117,7 +117,7 @@ When categorizing a task, the chain evaluates in this priority order:
1. **Cancelled** (highest) - `IsCancelled == true`
2. **Completed** - `NextDueDate == nil && len(Completions) > 0`
3. **In Progress** - `Status.Name == "In Progress"`
3. **In Progress** - `InProgress == true`
4. **Overdue** - `EffectiveDate < now`
5. **Due Soon** - `now <= EffectiveDate < threshold`
6. **Upcoming** (lowest/default) - Everything else
@@ -139,7 +139,7 @@ db.Model(&models.Task{}).
```go
// Load tasks with preloads
var tasks []models.Task
db.Preload("Status").Preload("Completions").
db.Preload("Completions").
Scopes(task.ScopeForResidence(residenceID)).
Find(&tasks)
@@ -329,18 +329,14 @@ db.Preload("Completions").Find(&tasks)
predicates.IsCompleted(task) // Correct result
```
### Forgetting to Preload Status
### In Progress Boolean Field
The `IsInProgress` predicate checks `task.Status.Name == "In Progress"`. Without preloading:
The `IsInProgress` predicate now uses a simple boolean field instead of a Status relation:
```go
// BAD: Status not loaded
db.Find(&tasks)
predicates.IsInProgress(task) // Nil pointer or always false
// GOOD: Preload status
db.Preload("Status").Find(&tasks)
predicates.IsInProgress(task) // Correct result
// IsInProgress uses the in_progress boolean field directly
// No preloading required for this check
predicates.IsInProgress(task) // Checks task.InProgress boolean
```
## Quick Reference Import