'date', 'due_date' => 'date', 'completed_at' => 'datetime', 'progress' => 'integer', 'estimated_hours' => 'decimal:2', 'order' => 'integer', ]; // ─── Basic Relationships ─────────────────────────────────────────── public function tenant() { return $this->belongsTo(Tenant::class); } public function project() { return $this->belongsTo(Project::class); } public function parent() { return $this->belongsTo(Task::class, 'parent_id'); } public function children() { return $this->hasMany(Task::class, 'parent_id'); } public function assignee() { return $this->belongsTo(User::class, 'assignee_id'); } public function reporter() { return $this->belongsTo(User::class, 'reporter_id'); } public function logs() { return $this->hasMany(TaskLog::class); } public function workLogs() { return $this->hasMany(WorkLog::class); } // ─── Dependency Relationships ────────────────────────────────────── /** * Tasks that this task depends on (predecessors). * e.g., "Task A depends on Task B" means B must finish before A starts. */ public function dependencies() { return $this->belongsToMany(Task::class, 'task_dependencies', 'task_id', 'depends_on_task_id') ->withPivot('dependency_type') ->withTimestamps(); } /** * Tasks that depend on this task (successors). * e.g., "Task A blocks Task C" means C cannot start until A finishes. */ public function dependents() { return $this->belongsToMany(Task::class, 'task_dependencies', 'depends_on_task_id', 'task_id') ->withPivot('dependency_type') ->withTimestamps(); } // ─── Scopes ──────────────────────────────────────────────────────── public function scopeByStatus($query, $status) { return $query->where('status', $status); } public function scopeByPriority($query, $priority) { return $query->where('priority', $priority); } public function scopeTopLevel($query) { return $query->whereNull('parent_id'); } public function scopeWithDependencies($query) { return $query->with(['dependencies', 'dependents', 'assignee', 'project']); } // ─── Accessors ───────────────────────────────────────────────────── public function getStatusLabelAttribute() { return [ 'pending' => __('task.status_pending'), 'todo' => __('task.status_pending'), 'in_progress' => __('task.status_in_progress'), 'review' => __('task.status_review'), 'done' => __('task.status_done'), 'cancelled' => __('task.status_cancelled'), ][$this->status] ?? $this->status; } /** * Check if all dependency tasks are completed. */ public function getCanStartAttribute(): bool { if ($this->dependencies->isEmpty()) { return true; } return $this->dependencies->every(fn ($dep) => $dep->status === 'done'); } /** * Check if this task is blocked by incomplete dependencies. */ public function getIsBlockedAttribute(): bool { return !$this->canStart; } /** * Get the blocking tasks (incomplete dependencies). */ public function getBlockingTasksAttribute() { return $this->dependencies->filter(fn ($dep) => $dep->status !== 'done'); } /** * Calculate duration in days. */ public function getDurationDaysAttribute(): ?int { if (!$this->start_date || !$this->due_date) { return null; } return $this->start_date->diffInDays($this->due_date) + 1; } }