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

@@ -103,15 +103,6 @@ func createCompletion(t *testing.T, taskID uint) {
}
}
// getInProgressStatusID returns the ID of the "In Progress" status
func getInProgressStatusID(t *testing.T) *uint {
var status models.TaskStatus
if err := testDB.Where("name = ?", "In Progress").First(&status).Error; err != nil {
t.Logf("In Progress status not found, skipping in-progress tests")
return nil
}
return &status.ID
}
// TaskTestCase defines a test scenario with expected categorization
type TaskTestCase struct {
@@ -147,8 +138,6 @@ func TestAllThreeLayersMatch(t *testing.T) {
in60Days := now.AddDate(0, 0, 60)
daysThreshold := 30
inProgressStatusID := getInProgressStatusID(t)
// Define all test cases with expected results for each layer
testCases := []TaskTestCase{
{
@@ -293,27 +282,23 @@ func TestAllThreeLayersMatch(t *testing.T) {
ExpectDueSoon: false,
ExpectUpcoming: false,
},
}
// Add in-progress test case only if status exists
if inProgressStatusID != nil {
testCases = append(testCases, TaskTestCase{
{
Name: "in_progress_overdue",
Task: &models.Task{
Title: "in_progress_overdue",
NextDueDate: timePtr(yesterday), // Would be overdue
StatusID: inProgressStatusID,
InProgress: true,
IsCancelled: false,
IsArchived: false,
},
ExpectedColumn: categorization.ColumnInProgress, // In Progress takes priority
ExpectCompleted: false,
ExpectActive: true,
ExpectOverdue: true, // Predicate says overdue (doesn't check status)
ExpectOverdue: true, // Predicate says overdue (doesn't check InProgress)
ExpectDueSoon: false,
ExpectUpcoming: false,
ExpectInProgress: true,
})
},
}
// Create all tasks in database
@@ -330,7 +315,6 @@ func TestAllThreeLayersMatch(t *testing.T) {
var allTasks []models.Task
err := testDB.
Preload("Completions").
Preload("Status").
Where("residence_id = ?", residenceID).
Find(&allTasks).Error
if err != nil {
@@ -490,26 +474,24 @@ func TestAllThreeLayersMatch(t *testing.T) {
}
})
// Test ScopeInProgress (if status exists)
if inProgressStatusID != nil {
t.Run("ScopeInProgress", func(t *testing.T) {
var scopeResults []models.Task
testDB.Model(&models.Task{}).
Scopes(scopes.ScopeForResidence(residenceID), scopes.ScopeInProgress).
Find(&scopeResults)
// Test ScopeInProgress
t.Run("ScopeInProgress", func(t *testing.T) {
var scopeResults []models.Task
testDB.Model(&models.Task{}).
Scopes(scopes.ScopeForResidence(residenceID), scopes.ScopeInProgress).
Find(&scopeResults)
predicateCount := 0
for _, task := range allTasks {
if predicates.IsInProgress(&task) {
predicateCount++
}
predicateCount := 0
for _, task := range allTasks {
if predicates.IsInProgress(&task) {
predicateCount++
}
}
if len(scopeResults) != predicateCount {
t.Errorf("ScopeInProgress returned %d, predicates found %d", len(scopeResults), predicateCount)
}
})
}
if len(scopeResults) != predicateCount {
t.Errorf("ScopeInProgress returned %d, predicates found %d", len(scopeResults), predicateCount)
}
})
})
// ========== TEST CATEGORIZATION MATCHES SCOPES FOR KANBAN ==========
@@ -527,7 +509,6 @@ func TestAllThreeLayersMatch(t *testing.T) {
t.Run("overdue_column", func(t *testing.T) {
var scopeResults []models.Task
testDB.Model(&models.Task{}).
Preload("Status").
Scopes(scopes.ScopeForResidence(residenceID), scopes.ScopeOverdue(now)).
Find(&scopeResults)
@@ -612,7 +593,7 @@ func TestSameDayOverdueConsistency(t *testing.T) {
// Reload with preloads
var loadedTask models.Task
testDB.Preload("Completions").Preload("Status").First(&loadedTask, task.ID)
testDB.Preload("Completions").First(&loadedTask, task.ID)
// All three layers should agree
predicateResult := predicates.IsOverdue(&loadedTask, now)