diff --git a/app/Services/SubscriptionService.php b/app/Services/SubscriptionService.php index fef576fd..c4741c1b 100644 --- a/app/Services/SubscriptionService.php +++ b/app/Services/SubscriptionService.php @@ -2,7 +2,7 @@ namespace App\Services; -use App\Models\ApiRequestLog; +use App\Models\Tenants\AiTokenUsage; use App\Models\Tenants\DataExport; use App\Models\Tenants\Payment; use App\Models\Tenants\Plan; @@ -304,13 +304,14 @@ public function resume(int $id): Subscription // ========================================================================= /** - * 사용량 조회 + * 사용량 조회 (이용현황 통합) + * - 사용자 수, 저장공간, AI 토큰, 구독 정보 */ public function usage(): array { $tenantId = $this->tenantId(); - $tenant = Tenant::with(['subscription.plan'])->findOrFail($tenantId); + $tenant = Tenant::withoutGlobalScopes()->findOrFail($tenantId); // 사용자 수 $userCount = $tenant->users()->count(); @@ -320,22 +321,42 @@ public function usage(): array $storageUsed = $tenant->storage_used ?? 0; $storageLimit = $tenant->storage_limit ?? 0; - // API 호출 수 (일간 - 로그는 1일 보관) - $apiCallsUsed = ApiRequestLog::where('tenant_id', $tenantId) - ->whereDate('created_at', now()->toDateString()) - ->count(); - // 기본 일간 API 호출 제한 (10,000회) - $apiCallsLimit = 10000; + // AI 토큰 (이번 달) + $currentMonth = now()->format('Y-m'); + $aiTokenLimit = $tenant->ai_token_limit ?? 1000000; - // 구독 정보 - $subscription = $tenant->subscription; - $remainingDays = null; - $planName = null; + $aiStats = AiTokenUsage::where('tenant_id', $tenantId) + ->whereRaw("DATE_FORMAT(created_at, '%Y-%m') = ?", [$currentMonth]) + ->selectRaw(' + COUNT(*) as total_requests, + COALESCE(SUM(prompt_tokens), 0) as prompt_tokens, + COALESCE(SUM(completion_tokens), 0) as completion_tokens, + COALESCE(SUM(total_tokens), 0) as total_tokens, + COALESCE(SUM(cost_usd), 0) as cost_usd, + COALESCE(SUM(cost_krw), 0) as cost_krw + ') + ->first(); - if ($subscription && $subscription->is_valid) { - $remainingDays = $subscription->remaining_days; - $planName = $subscription->plan?->name; - } + $aiByModel = AiTokenUsage::where('tenant_id', $tenantId) + ->whereRaw("DATE_FORMAT(created_at, '%Y-%m') = ?", [$currentMonth]) + ->selectRaw(' + model, + COUNT(*) as requests, + COALESCE(SUM(total_tokens), 0) as total_tokens, + COALESCE(SUM(cost_krw), 0) as cost_krw + ') + ->groupBy('model') + ->orderByDesc('total_tokens') + ->get(); + + $totalTokens = (int) $aiStats->total_tokens; + $aiPercentage = $aiTokenLimit > 0 ? round(($totalTokens / $aiTokenLimit) * 100, 1) : 0; + + // 구독 정보 (tenant_id 기반 최신 활성 구독) + $subscription = Subscription::with('plan') + ->where('tenant_id', $tenantId) + ->orderByDesc('created_at') + ->first(); return [ 'users' => [ @@ -350,17 +371,34 @@ public function usage(): array 'limit_formatted' => $tenant->getStorageLimitFormatted(), 'percentage' => $storageLimit > 0 ? round(($storageUsed / $storageLimit) * 100, 1) : 0, ], - 'api_calls' => [ - 'used' => $apiCallsUsed, - 'limit' => $apiCallsLimit, - 'percentage' => $apiCallsLimit > 0 ? round(($apiCallsUsed / $apiCallsLimit) * 100, 1) : 0, + 'ai_tokens' => [ + 'period' => $currentMonth, + 'total_requests' => (int) $aiStats->total_requests, + 'total_tokens' => $totalTokens, + 'prompt_tokens' => (int) $aiStats->prompt_tokens, + 'completion_tokens' => (int) $aiStats->completion_tokens, + 'limit' => $aiTokenLimit, + 'percentage' => $aiPercentage, + 'cost_usd' => round((float) $aiStats->cost_usd, 4), + 'cost_krw' => round((float) $aiStats->cost_krw), + 'warning_threshold' => 80, + 'is_over_limit' => $totalTokens > $aiTokenLimit, + 'by_model' => $aiByModel->map(fn ($m) => [ + 'model' => $m->model, + 'requests' => (int) $m->requests, + 'total_tokens' => (int) $m->total_tokens, + 'cost_krw' => round((float) $m->cost_krw), + ])->values()->toArray(), ], 'subscription' => [ - 'plan' => $planName, - 'status' => $subscription?->status, - 'remaining_days' => $remainingDays, + 'plan' => $subscription?->plan?->name, + 'monthly_fee' => (int) ($subscription?->plan?->price ?? 0), + 'status' => $subscription?->status ?? 'active', 'started_at' => $subscription?->started_at?->toDateString(), 'ended_at' => $subscription?->ended_at?->toDateString(), + 'remaining_days' => $subscription?->ended_at + ? max(0, (int) now()->diffInDays($subscription->ended_at, false)) + : null, ], ]; }