'integer', 'created_at' => 'datetime', 'started_at' => 'datetime', 'completed_at' => 'datetime', 'duration_ms' => 'integer', 'total_steps' => 'integer', 'completed_steps' => 'integer', 'failed_step' => 'integer', 'execution_log' => 'array', 'input_variables' => 'array', 'api_logs' => 'array', 'executed_by' => 'integer', ]; /** * 상태별 필터링 */ public function scopeStatus($query, string $status) { return $query->where('status', $status); } /** * 성공한 실행만 조회 */ public function scopeSuccessful($query) { return $query->where('status', self::STATUS_SUCCESS); } /** * 실패한 실행만 조회 */ public function scopeFailed($query) { return $query->where('status', self::STATUS_FAILED); } /** * 관계: 플로우 */ public function flow(): BelongsTo { return $this->belongsTo(AdminApiFlow::class, 'flow_id'); } /** * 관계: 실행자 */ public function executor(): BelongsTo { return $this->belongsTo(User::class, 'executed_by'); } /** * 실행 중인지 확인 */ public function isRunning(): bool { return $this->status === self::STATUS_RUNNING; } /** * 완료되었는지 확인 (성공/실패/부분 포함) */ public function isCompleted(): bool { return in_array($this->status, [ self::STATUS_SUCCESS, self::STATUS_FAILED, self::STATUS_PARTIAL, ]); } /** * 진행률 반환 (0-100) */ public function getProgressAttribute(): int { if (! $this->total_steps || $this->total_steps === 0) { return 0; } return (int) round(($this->completed_steps / $this->total_steps) * 100); } /** * 상태 라벨 반환 (한글) */ public function getStatusLabelAttribute(): string { return match ($this->status) { self::STATUS_PENDING => '대기 중', self::STATUS_RUNNING => '실행 중', self::STATUS_SUCCESS => '성공', self::STATUS_FAILED => '실패', self::STATUS_PARTIAL => '부분 성공', default => $this->status, }; } /** * 상태 색상 반환 (Tailwind CSS class) */ public function getStatusColorAttribute(): string { return match ($this->status) { self::STATUS_PENDING => 'bg-gray-100 text-gray-600', self::STATUS_RUNNING => 'bg-blue-100 text-blue-700', self::STATUS_SUCCESS => 'bg-green-100 text-green-700', self::STATUS_FAILED => 'bg-red-100 text-red-700', self::STATUS_PARTIAL => 'bg-yellow-100 text-yellow-700', default => 'bg-gray-100 text-gray-600', }; } /** * API 로그 개수 반환 */ public function getApiLogCountAttribute(): int { return is_array($this->api_logs) ? count($this->api_logs) : 0; } /** * API 로그에 오류가 있는지 확인 */ public function hasApiErrors(): bool { if (! is_array($this->api_logs)) { return false; } foreach ($this->api_logs as $log) { if (isset($log['type']) && $log['type'] === 'response') { $status = $log['status'] ?? 0; if ($status >= 400) { return true; } } } return false; } }