Replace status_id with in_progress boolean field

- Remove task_statuses lookup table and StatusID foreign key
- Add InProgress boolean field to Task model
- Add database migration (005_replace_status_with_in_progress)
- Update all handlers, services, and repositories
- Update admin frontend to display in_progress as checkbox/boolean
- Remove Task Statuses tab from admin lookups page
- Update tests to use InProgress instead of StatusID
- Task categorization now uses InProgress for kanban column assignment

🤖 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 20:48:16 -06:00
parent cb250f108b
commit c5b0225422
43 changed files with 353 additions and 753 deletions

View File

@@ -73,22 +73,22 @@ func ScopeNotCompleted(db *gorm.DB) *gorm.DB {
)
}
// ScopeInProgress filters to tasks with status "In Progress".
// ScopeInProgress filters to tasks marked as in progress.
//
// Predicate equivalent: IsInProgress(task)
//
// SQL: Joins task_taskstatus and filters by name = 'In Progress'
// SQL: in_progress = true
func ScopeInProgress(db *gorm.DB) *gorm.DB {
return db.Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
Where("task_taskstatus.name = ?", "In Progress")
return db.Where("in_progress = ?", true)
}
// ScopeNotInProgress excludes tasks with status "In Progress".
// ScopeNotInProgress excludes tasks marked as in progress.
//
// Predicate equivalent: !IsInProgress(task)
//
// SQL: in_progress = false
func ScopeNotInProgress(db *gorm.DB) *gorm.DB {
return db.Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
Where("task_taskstatus.name != ? OR task_taskstatus.name IS NULL", "In Progress")
return db.Where("in_progress = ?", false)
}
// =============================================================================

View File

@@ -54,7 +54,6 @@ func TestMain(m *testing.M) {
err = testDB.AutoMigrate(
&models.Task{},
&models.TaskCompletion{},
&models.TaskStatus{},
&models.Residence{},
)
if err != nil {
@@ -77,7 +76,6 @@ func cleanupTestData() {
}
testDB.Exec("DELETE FROM task_taskcompletion WHERE task_id IN (SELECT id FROM task_task WHERE title LIKE 'test_%')")
testDB.Exec("DELETE FROM task_task WHERE title LIKE 'test_%'")
testDB.Exec("DELETE FROM task_taskstatus WHERE name LIKE 'test_%'")
testDB.Exec("DELETE FROM residence_residence WHERE name LIKE 'test_%'")
}
@@ -102,16 +100,6 @@ func createTestResidence(t *testing.T) uint {
return residence.ID
}
// createTestStatus creates a test status and returns it
func createTestStatus(t *testing.T, name string) *models.TaskStatus {
status := &models.TaskStatus{
Name: "test_" + name,
}
if err := testDB.Create(status).Error; err != nil {
t.Fatalf("Failed to create test status: %v", err)
}
return status
}
// createTestTask creates a task with the given properties
func createTestTask(t *testing.T, residenceID uint, task *models.Task) *models.Task {
@@ -587,41 +575,18 @@ func TestScopeInProgressMatchesPredicate(t *testing.T) {
}
residenceID := createTestResidence(t)
// For InProgress, we need to use the exact status name "In Progress" because
// the scope joins on task_taskstatus.name = 'In Progress'
// First, try to find existing "In Progress" status, or create one
var inProgressStatus models.TaskStatus
if err := testDB.Where("name = ?", "In Progress").First(&inProgressStatus).Error; err != nil {
// Create it if it doesn't exist
inProgressStatus = models.TaskStatus{Name: "In Progress"}
testDB.Create(&inProgressStatus)
}
var pendingStatus models.TaskStatus
if err := testDB.Where("name = ?", "Pending").First(&pendingStatus).Error; err != nil {
pendingStatus = models.TaskStatus{Name: "Pending"}
testDB.Create(&pendingStatus)
}
defer cleanupTestData()
// In progress task
createTestTask(t, residenceID, &models.Task{
Title: "in_progress",
StatusID: &inProgressStatus.ID,
Title: "in_progress",
InProgress: true,
})
// Not in progress: different status
// Not in progress: InProgress is false
createTestTask(t, residenceID, &models.Task{
Title: "pending",
StatusID: &pendingStatus.ID,
})
// Not in progress: no status
createTestTask(t, residenceID, &models.Task{
Title: "no_status",
StatusID: nil,
Title: "not_in_progress",
InProgress: false,
})
// Query using scope
@@ -633,9 +598,9 @@ func TestScopeInProgressMatchesPredicate(t *testing.T) {
t.Fatalf("Scope query failed: %v", err)
}
// Query all tasks with status preloaded and filter with predicate
// Query all tasks and filter with predicate
var allTasks []models.Task
testDB.Preload("Status").Where("residence_id = ?", residenceID).Find(&allTasks)
testDB.Where("residence_id = ?", residenceID).Find(&allTasks)
var predicateResults []models.Task
for _, task := range allTasks {