Consolidate task logic into single source of truth (DRY refactor)
This refactor eliminates duplicate task logic across the codebase by creating a centralized task package with three layers: - predicates/: Pure Go functions defining task state logic (IsCompleted, IsOverdue, IsDueSoon, IsUpcoming, IsActive, IsInProgress, EffectiveDate) - scopes/: GORM scope functions mirroring predicates for database queries - categorization/: Chain of Responsibility pattern for kanban column assignment Key fixes: - Fixed PostgreSQL DATE vs TIMESTAMP comparison bug in scopes (added explicit ::timestamp casts) that caused summary/kanban count mismatches - Fixed models/task.go IsOverdue() and IsDueSoon() to use EffectiveDate (NextDueDate ?? DueDate) instead of only DueDate - Removed duplicate isTaskCompleted() helpers from task_repo.go and task_button_types.go Files refactored to use consolidated logic: - task_repo.go: Uses scopes for statistics, predicates for filtering - task_button_types.go: Uses predicates instead of inline logic - responses/task.go: Delegates to categorization package - dashboard_handler.go: Uses scopes for task statistics - residence_service.go: Uses predicates for report generation - worker/jobs/handler.go: Documented SQL with predicate references Added comprehensive tests: - predicates_test.go: Unit tests for all predicate functions - scopes_test.go: Integration tests verifying scopes match predicates - consistency_test.go: Three-layer consistency tests ensuring predicates, scopes, and categorization all return identical results 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/treytartt/casera-api/internal/dto/responses"
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
"github.com/treytartt/casera-api/internal/repositories"
|
||||
"github.com/treytartt/casera-api/internal/task/predicates"
|
||||
)
|
||||
|
||||
// Common errors
|
||||
@@ -553,8 +554,9 @@ func (s *ResidenceService) GenerateTasksReport(residenceID, userID uint) (*Tasks
|
||||
}
|
||||
|
||||
for i, task := range tasks {
|
||||
// Determine if task is completed (has completions)
|
||||
isCompleted := len(task.Completions) > 0
|
||||
// Use predicates from internal/task/predicates as single source of truth
|
||||
isCompleted := predicates.IsCompleted(&task)
|
||||
isOverdue := predicates.IsOverdue(&task, now)
|
||||
|
||||
taskData := TaskReportData{
|
||||
ID: task.ID,
|
||||
@@ -574,17 +576,19 @@ func (s *ResidenceService) GenerateTasksReport(residenceID, userID uint) (*Tasks
|
||||
if task.Status != nil {
|
||||
taskData.Status = task.Status.Name
|
||||
}
|
||||
if task.DueDate != nil {
|
||||
taskData.DueDate = task.DueDate
|
||||
// Use effective date for report (NextDueDate ?? DueDate)
|
||||
effectiveDate := predicates.EffectiveDate(&task)
|
||||
if effectiveDate != nil {
|
||||
taskData.DueDate = effectiveDate
|
||||
}
|
||||
|
||||
report.Tasks[i] = taskData
|
||||
|
||||
if isCompleted {
|
||||
report.Completed++
|
||||
} else if !task.IsCancelled && !task.IsArchived {
|
||||
} else if predicates.IsActive(&task) {
|
||||
report.Pending++
|
||||
if task.DueDate != nil && task.DueDate.Before(now) {
|
||||
if isOverdue {
|
||||
report.Overdue++
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user