'datetime']; public function project() { return $this->belongsTo(Project::class); } public function feature() { return $this->belongsTo(Feature::class); } public function inspection() { return $this->belongsTo(Inspection::class); } public function reporter() { return $this->belongsTo(User::class, 'reported_by'); } public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); } public function media() { return $this->morphMany(Media::class, 'mediable'); } public function tasks() { return $this->hasMany(IssueTask::class)->orderBy('order')->orderBy('id'); } public function comments() { return $this->hasMany(IssueComment::class)->orderBy('created_at'); } public function scopeOpen($q) { return $q->where('status', 'open'); } public function scopeCritical($q) { return $q->where('priority', 'critical'); } /** Resolution progress derived from the checklist: done tasks / total. */ public function getProgressAttribute(): int { $total = $this->tasks->count(); if ($total === 0) { return in_array($this->status, ['resolved', 'closed'], true) ? 100 : 0; } return (int) round($this->tasks->where('is_done', true)->count() / $total * 100); } /** True when there is at least one task and all of them are done. */ public function getTasksCompleteAttribute(): bool { return $this->tasks->count() > 0 && $this->tasks->every(fn ($t) => $t->is_done); } public function getPriorityColorAttribute(): string { return match($this->priority) { 'low' => '#6b7280', 'medium' => '#f59e0b', 'high' => '#ef4444', 'critical' => '#7c3aed', default => '#6b7280', }; } public function getStatusColorAttribute(): string { return match($this->status) { 'open' => '#ef4444', 'in_review' => '#f59e0b', 'resolved' => '#10b981', 'closed' => '#6b7280', default => '#6b7280', }; } /** Human label (Spanish) for each issue type. */ public static function typeLabels(): array { return [ 'defect' => 'Defecto', 'safety' => 'Seguridad', 'quality' => 'Calidad', 'documentation' => 'Documentación', 'other' => 'Otro', ]; } public function getTypeLabelAttribute(): string { return self::typeLabels()[$this->type] ?? ucfirst((string) $this->type); } public function getTypeColorAttribute(): string { return match($this->type) { 'defect' => '#ef4444', 'safety' => '#f97316', 'quality' => '#0ea5e9', 'documentation' => '#8b5cf6', default => '#6b7280', }; } }