tenantId(); $query = AiTokenUsage::where('tenant_id', $tenantId) ->when($params['start_date'] ?? null, fn ($q, $d) => $q->whereDate('created_at', '>=', $d)) ->when($params['end_date'] ?? null, fn ($q, $d) => $q->whereDate('created_at', '<=', $d)) ->when($params['menu_name'] ?? null, fn ($q, $m) => $q->where('menu_name', $m)); // 통계 (페이지네이션 전 전체 집계) $stats = (clone $query)->selectRaw(' COUNT(*) as total_count, COALESCE(SUM(prompt_tokens), 0) as total_prompt_tokens, COALESCE(SUM(completion_tokens), 0) as total_completion_tokens, COALESCE(SUM(total_tokens), 0) as total_total_tokens, COALESCE(SUM(cost_usd), 0) as total_cost_usd, COALESCE(SUM(cost_krw), 0) as total_cost_krw ')->first(); // 목록 (페이지네이션) $items = $query->with('creator:id,name') ->orderByDesc('created_at') ->paginate($params['per_page'] ?? 20); // 메뉴명 필터 옵션 $menuNames = AiTokenUsage::where('tenant_id', $tenantId) ->select('menu_name') ->distinct() ->orderBy('menu_name') ->pluck('menu_name'); return [ 'items' => $items, 'stats' => [ 'total_count' => (int) $stats->total_count, 'total_prompt_tokens' => (int) $stats->total_prompt_tokens, 'total_completion_tokens' => (int) $stats->total_completion_tokens, 'total_total_tokens' => (int) $stats->total_total_tokens, 'total_cost_usd' => (float) $stats->total_cost_usd, 'total_cost_krw' => (float) $stats->total_cost_krw, ], 'menu_names' => $menuNames, ]; } /** * 단가 설정 조회 (읽기 전용) */ public function getPricing(): array { $configs = AiPricingConfig::where('is_active', true) ->orderBy('provider') ->get(); return [ 'pricing' => $configs, 'exchange_rate' => AiPricingConfig::getExchangeRate(), ]; } }