whereNull('deleted_at') ->order('sort_order', 'asc') ->order('id', 'asc') ->select() ->all(); if (empty($themes)) { return []; } $themeIds = array_map(static function ($item) { return (int) $item->id; }, $themes); $levels = AchievementLevel::whereIn('theme_id', $themeIds) ->whereNull('deleted_at') ->order('required_days', 'asc') ->order('sort_order', 'asc') ->select() ->all(); $groupedLevels = []; foreach ($levels as $level) { $groupedLevels[(int) $level->theme_id][] = [ 'id' => (int) $level->id, 'theme_id' => (int) $level->theme_id, 'name' => (string) $level->name, 'icon' => (string) $level->icon, 'required_days' => (int) $level->required_days, 'sort_order' => (int) $level->sort_order, ]; } return array_map(static function ($theme) use ($groupedLevels) { return [ 'id' => (int) $theme->id, 'name' => (string) $theme->name, 'key' => (string) $theme->key, 'icon' => (string) $theme->icon, 'sort_order' => (int) $theme->sort_order, 'is_active' => (bool) $theme->is_active, 'levels' => $groupedLevels[(int) $theme->id] ?? [], ]; }, $themes); } public function getUserAchievement(int $themeId, int $days): ?array { $theme = AchievementTheme::where('id', $themeId)->whereNull('deleted_at')->find(); if (!$theme) { return null; } $levels = AchievementLevel::where('theme_id', $themeId) ->whereNull('deleted_at') ->order('required_days', 'asc') ->order('sort_order', 'asc') ->select() ->all(); $current = null; $next = null; foreach ($levels as $index => $level) { if ($days >= (int) $level->required_days) { $current = $level; $next = $levels[$index + 1] ?? null; } } $progress = 0.0; if ($current && $next) { $rangeTotal = (int) $next->required_days - (int) $current->required_days; if ($rangeTotal > 0) { $progress = max(0, min(1, ($days - (int) $current->required_days) / $rangeTotal)); } } elseif ($current) { $progress = 1.0; } return [ 'theme_id' => (int) $theme->id, 'theme_name' => (string) $theme->name, 'theme_key' => (string) $theme->key, 'theme_icon' => (string) $theme->icon, 'days' => max(0, $days), 'current' => $current ? $this->formatLevel($current) : null, 'next' => $next ? $this->formatLevel($next) : null, 'progress' => $progress, ]; } private function formatLevel(AchievementLevel $level): array { return [ 'id' => (int) $level->id, 'theme_id' => (int) $level->theme_id, 'name' => (string) $level->name, 'icon' => (string) $level->icon, 'required_days' => (int) $level->required_days, 'sort_order' => (int) $level->sort_order, ]; } }