// Package task provides consolidated task domain logic. // // This package serves as the single entry point for all task-related business logic. // It re-exports functions from sub-packages for convenient imports. // // Architecture: // // predicates/ - Pure Go predicate functions (SINGLE SOURCE OF TRUTH) // scopes/ - GORM scope functions (SQL mirrors of predicates) // categorization/ - Chain of Responsibility for kanban categorization // // Usage: // // import "github.com/treytartt/honeydue-api/internal/task" // // // Use predicates for in-memory checks // if task.IsCompleted(myTask) { ... } // // // Use scopes for database queries // db.Scopes(task.ScopeOverdue(now)).Find(&tasks) // // // Use categorization for kanban column determination // column := task.CategorizeTask(myTask, 30) // // For more details, see docs/TASK_LOGIC_ARCHITECTURE.md package task import ( "time" "github.com/treytartt/honeydue-api/internal/models" "github.com/treytartt/honeydue-api/internal/task/categorization" "github.com/treytartt/honeydue-api/internal/task/predicates" "github.com/treytartt/honeydue-api/internal/task/scopes" "gorm.io/gorm" ) // ============================================================================= // RE-EXPORTED TYPES // ============================================================================= // KanbanColumn represents the possible kanban column names type KanbanColumn = categorization.KanbanColumn // Column constants const ( ColumnOverdue = categorization.ColumnOverdue ColumnDueSoon = categorization.ColumnDueSoon ColumnUpcoming = categorization.ColumnUpcoming ColumnInProgress = categorization.ColumnInProgress ColumnCompleted = categorization.ColumnCompleted ColumnCancelled = categorization.ColumnCancelled ) // ============================================================================= // RE-EXPORTED PREDICATES // These are the SINGLE SOURCE OF TRUTH for task logic // ============================================================================= // IsCompleted returns true if a task is considered "completed" per kanban rules. // A task is completed when NextDueDate is nil AND has at least one completion. func IsCompleted(task *models.Task) bool { return predicates.IsCompleted(task) } // IsActive returns true if the task is not cancelled and not archived. func IsActive(task *models.Task) bool { return predicates.IsActive(task) } // IsCancelled returns true if the task has been cancelled. func IsCancelled(task *models.Task) bool { return predicates.IsCancelled(task) } // IsArchived returns true if the task has been archived. func IsArchived(task *models.Task) bool { return predicates.IsArchived(task) } // IsInProgress returns true if the task has status "In Progress". func IsInProgress(task *models.Task) bool { return predicates.IsInProgress(task) } // EffectiveDate returns the date used for scheduling calculations. // Prefers NextDueDate, falls back to DueDate. func EffectiveDate(task *models.Task) *time.Time { return predicates.EffectiveDate(task) } // IsOverdue returns true if the task's effective date is in the past. func IsOverdue(task *models.Task, now time.Time) bool { return predicates.IsOverdue(task, now) } // IsDueSoon returns true if the task's effective date is within the threshold. func IsDueSoon(task *models.Task, now time.Time, daysThreshold int) bool { return predicates.IsDueSoon(task, now, daysThreshold) } // IsUpcoming returns true if the task is due after the threshold or has no due date. func IsUpcoming(task *models.Task, now time.Time, daysThreshold int) bool { return predicates.IsUpcoming(task, now, daysThreshold) } // HasCompletions returns true if the task has at least one completion record. func HasCompletions(task *models.Task) bool { return predicates.HasCompletions(task) } // GetCompletionCount returns the number of completions for a task. // Supports both preloaded Completions slice and computed CompletionCount field. func GetCompletionCount(task *models.Task) int { return predicates.GetCompletionCount(task) } // IsRecurring returns true if the task has a recurring frequency. func IsRecurring(task *models.Task) bool { return predicates.IsRecurring(task) } // IsOneTime returns true if the task is a one-time (non-recurring) task. func IsOneTime(task *models.Task) bool { return predicates.IsOneTime(task) } // ============================================================================= // RE-EXPORTED SCOPES // These are SQL mirrors of the predicates for database queries // ============================================================================= // ScopeActive filters to tasks that are not cancelled and not archived. func ScopeActive(db *gorm.DB) *gorm.DB { return scopes.ScopeActive(db) } // ScopeCancelled filters to cancelled tasks only. func ScopeCancelled(db *gorm.DB) *gorm.DB { return scopes.ScopeCancelled(db) } // ScopeArchived filters to archived tasks only. func ScopeArchived(db *gorm.DB) *gorm.DB { return scopes.ScopeArchived(db) } // ScopeCompleted filters to completed tasks. func ScopeCompleted(db *gorm.DB) *gorm.DB { return scopes.ScopeCompleted(db) } // ScopeNotCompleted excludes completed tasks. func ScopeNotCompleted(db *gorm.DB) *gorm.DB { return scopes.ScopeNotCompleted(db) } // ScopeInProgress filters to tasks with status "In Progress". func ScopeInProgress(db *gorm.DB) *gorm.DB { return scopes.ScopeInProgress(db) } // ScopeNotInProgress excludes tasks with status "In Progress". func ScopeNotInProgress(db *gorm.DB) *gorm.DB { return scopes.ScopeNotInProgress(db) } // ScopeOverdue returns a scope for overdue tasks. func ScopeOverdue(now time.Time) func(db *gorm.DB) *gorm.DB { return scopes.ScopeOverdue(now) } // ScopeDueSoon returns a scope for tasks due within the threshold. func ScopeDueSoon(now time.Time, daysThreshold int) func(db *gorm.DB) *gorm.DB { return scopes.ScopeDueSoon(now, daysThreshold) } // ScopeUpcoming returns a scope for tasks due after the threshold or with no due date. func ScopeUpcoming(now time.Time, daysThreshold int) func(db *gorm.DB) *gorm.DB { return scopes.ScopeUpcoming(now, daysThreshold) } // ScopeDueInRange returns a scope for tasks with effective date in a range. func ScopeDueInRange(start, end time.Time) func(db *gorm.DB) *gorm.DB { return scopes.ScopeDueInRange(start, end) } // ScopeHasDueDate filters to tasks that have an effective due date. func ScopeHasDueDate(db *gorm.DB) *gorm.DB { return scopes.ScopeHasDueDate(db) } // ScopeNoDueDate filters to tasks that have no effective due date. func ScopeNoDueDate(db *gorm.DB) *gorm.DB { return scopes.ScopeNoDueDate(db) } // ScopeForResidence filters tasks by a single residence ID. func ScopeForResidence(residenceID uint) func(db *gorm.DB) *gorm.DB { return scopes.ScopeForResidence(residenceID) } // ScopeForResidences filters tasks by multiple residence IDs. func ScopeForResidences(residenceIDs []uint) func(db *gorm.DB) *gorm.DB { return scopes.ScopeForResidences(residenceIDs) } // ScopeHasCompletions filters to tasks that have at least one completion. func ScopeHasCompletions(db *gorm.DB) *gorm.DB { return scopes.ScopeHasCompletions(db) } // ScopeNoCompletions filters to tasks that have no completions. func ScopeNoCompletions(db *gorm.DB) *gorm.DB { return scopes.ScopeNoCompletions(db) } // ScopeOrderByDueDate orders tasks by effective due date ascending, nulls last. func ScopeOrderByDueDate(db *gorm.DB) *gorm.DB { return scopes.ScopeOrderByDueDate(db) } // ScopeOrderByPriority orders tasks by priority level descending (urgent first). func ScopeOrderByPriority(db *gorm.DB) *gorm.DB { return scopes.ScopeOrderByPriority(db) } // ScopeOrderByCreatedAt orders tasks by creation date descending (newest first). func ScopeOrderByCreatedAt(db *gorm.DB) *gorm.DB { return scopes.ScopeOrderByCreatedAt(db) } // ScopeKanbanOrder applies the standard kanban ordering. func ScopeKanbanOrder(db *gorm.DB) *gorm.DB { return scopes.ScopeKanbanOrder(db) } // ============================================================================= // RE-EXPORTED CATEGORIZATION // ============================================================================= // CategorizeTask determines which kanban column a task belongs to. func CategorizeTask(task *models.Task, daysThreshold int) KanbanColumn { return categorization.CategorizeTask(task, daysThreshold) } // DetermineKanbanColumn is a convenience function that returns the column as a string. func DetermineKanbanColumn(task *models.Task, daysThreshold int) string { return categorization.DetermineKanbanColumn(task, daysThreshold) } // CategorizeTasksIntoColumns categorizes multiple tasks into their respective columns. func CategorizeTasksIntoColumns(tasks []models.Task, daysThreshold int) map[KanbanColumn][]models.Task { return categorization.CategorizeTasksIntoColumns(tasks, daysThreshold) } // NewChain creates a new categorization chain for custom usage. func NewChain() *categorization.Chain { return categorization.NewChain() }