vernova/app/Models/Task.php
2026-07-26 19:58:27 +03:30

181 lines
4.8 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'tenant_id',
'project_id',
'parent_id',
'assignee_id',
'reporter_id',
'title',
'description',
'status',
'priority',
'start_date',
'due_date',
'progress',
'estimated_hours',
'completed_at',
'order',
];
protected $casts = [
'start_date' => '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;
}
}