'date', 'end_date' => 'date', 'progress' => 'integer', 'sort_order' => 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', 'deleted_by' => 'integer', ]; // 상태 public const STATUS_PLANNED = 'planned'; public const STATUS_IN_PROGRESS = 'in_progress'; public const STATUS_COMPLETED = 'completed'; public const STATUS_DELAYED = 'delayed'; public const STATUS_CANCELLED = 'cancelled'; // 카테고리 public const CATEGORY_GENERAL = 'general'; public const CATEGORY_PRODUCT = 'product'; public const CATEGORY_INFRASTRUCTURE = 'infrastructure'; public const CATEGORY_BUSINESS = 'business'; public const CATEGORY_HR = 'hr'; // 우선순위 public const PRIORITY_LOW = 'low'; public const PRIORITY_MEDIUM = 'medium'; public const PRIORITY_HIGH = 'high'; public const PRIORITY_CRITICAL = 'critical'; // Phase public const PHASE_1 = 'phase_1'; public const PHASE_2 = 'phase_2'; public const PHASE_3 = 'phase_3'; public const PHASE_4 = 'phase_4'; public static function getStatuses(): array { return [ self::STATUS_PLANNED => '계획', self::STATUS_IN_PROGRESS => '진행중', self::STATUS_COMPLETED => '완료', self::STATUS_DELAYED => '지연', self::STATUS_CANCELLED => '취소', ]; } public static function getCategories(): array { return [ self::CATEGORY_GENERAL => '일반', self::CATEGORY_PRODUCT => '제품', self::CATEGORY_INFRASTRUCTURE => '인프라', self::CATEGORY_BUSINESS => '사업', self::CATEGORY_HR => '인사', ]; } public static function getPriorities(): array { return [ self::PRIORITY_LOW => '낮음', self::PRIORITY_MEDIUM => '보통', self::PRIORITY_HIGH => '높음', self::PRIORITY_CRITICAL => '긴급', ]; } public static function getPhases(): array { return [ self::PHASE_1 => 'Phase 1 — 코어 실증', self::PHASE_2 => 'Phase 2 — 3~5사 확장', self::PHASE_3 => 'Phase 3 — SaaS 전환', self::PHASE_4 => 'Phase 4 — 스케일업', ]; } public function scopeStatus($query, string $status) { return $query->where('status', $status); } public function scopeCategory($query, string $category) { return $query->where('category', $category); } public function scopePhase($query, string $phase) { return $query->where('phase', $phase); } public function milestones(): HasMany { return $this->hasMany(AdminRoadmapMilestone::class, 'plan_id'); } public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } public function updater(): BelongsTo { return $this->belongsTo(User::class, 'updated_by'); } public function getStatusLabelAttribute(): string { return self::getStatuses()[$this->status] ?? $this->status; } public function getStatusColorAttribute(): string { return match ($this->status) { self::STATUS_PLANNED => 'bg-gray-100 text-gray-800', self::STATUS_IN_PROGRESS => 'bg-blue-100 text-blue-800', self::STATUS_COMPLETED => 'bg-green-100 text-green-800', self::STATUS_DELAYED => 'bg-red-100 text-red-800', self::STATUS_CANCELLED => 'bg-yellow-100 text-yellow-800', default => 'bg-gray-100 text-gray-800', }; } public function getCategoryLabelAttribute(): string { return self::getCategories()[$this->category] ?? $this->category; } public function getPriorityLabelAttribute(): string { return self::getPriorities()[$this->priority] ?? $this->priority; } public function getPriorityColorAttribute(): string { return match ($this->priority) { self::PRIORITY_LOW => 'bg-gray-100 text-gray-600', self::PRIORITY_MEDIUM => 'bg-blue-100 text-blue-700', self::PRIORITY_HIGH => 'bg-orange-100 text-orange-700', self::PRIORITY_CRITICAL => 'bg-red-100 text-red-700', default => 'bg-gray-100 text-gray-600', }; } public function getPhaseLabelAttribute(): string { return self::getPhases()[$this->phase] ?? $this->phase; } public function getCalculatedProgressAttribute(): int { $total = $this->milestones()->count(); if ($total === 0) { return $this->progress; } $completed = $this->milestones()->where('status', AdminRoadmapMilestone::STATUS_COMPLETED)->count(); return (int) round(($completed / $total) * 100); } public function getMilestoneStatsAttribute(): array { return [ 'total' => $this->milestones()->count(), 'pending' => $this->milestones()->where('status', AdminRoadmapMilestone::STATUS_PENDING)->count(), 'completed' => $this->milestones()->where('status', AdminRoadmapMilestone::STATUS_COMPLETED)->count(), ]; } public function getPeriodAttribute(): string { if ($this->start_date && $this->end_date) { return $this->start_date->format('Y.m').' ~ '.$this->end_date->format('Y.m'); } if ($this->start_date) { return $this->start_date->format('Y.m').' ~'; } return '-'; } }