diff --git a/INDEX.md b/INDEX.md index 9380f09..1492f30 100644 --- a/INDEX.md +++ b/INDEX.md @@ -294,6 +294,7 @@ DB 도메인별: | [bom-tree-visualization-request.md](plans/bom-tree-visualization-request.md) | BOM Tree 시각화 React 구현 요청 (API 완료, 재귀 트리 UI) | | [ai-token-usage-service-migration.md](plans/ai-token-usage-service-migration.md) | AI 토큰사용량 서비스 이관 기획 (MNG→API+React, GCS→R2 저장소 차이) | | [notification-sound-react-request.md](plans/notification-sound-react-request.md) | 알림설정 soundType React 구현 요청 (음원 배치, 미리듣기 실제 재생, API 연동 완료) | +| [usage-subscription-unification.md](plans/usage-subscription-unification.md) | 이용현황+구독관리 통합 계획 (AI 토큰 사용량, 저장공간, 메뉴 통합) | ### frontend/integration/ — 프론트엔드 개발 가이드 diff --git a/plans/usage-subscription-unification.md b/plans/usage-subscription-unification.md new file mode 100644 index 0000000..1f32225 --- /dev/null +++ b/plans/usage-subscription-unification.md @@ -0,0 +1,304 @@ +# 이용현황 + 구독관리 통합 개선 계획 + +> **작성일**: 2026-03-18 +> **상태**: 계획 수립 + +--- + +## 1. 개요 + +### 1.1 목적 + +React 서비스의 **이용현황**(`/usage`)과 **구독관리**(`/subscription`) 메뉴를 하나로 통합하고, 현재 mock/하드코딩 데이터를 실제 DB 기반으로 개선한다. + +### 1.2 배경 + +- `/usage` 페이지는 EmptyPage(미구현) +- `/subscription` 페이지는 구현되어 있으나, 일부 데이터가 하드코딩(API 호출 상한 10,000) +- 사용자가 원하는 핵심 정보: **AI 토큰 사용량** + **서버 저장공간** +- 메뉴가 분산되어 있어 하나로 통합 + +### 1.3 핵심 원칙 + +``` +✅ 하나의 메뉴(이용현황)에 구독 + 사용량 통합 표시 +✅ 모든 데이터는 실제 DB에서 조회 +✅ AI 토큰 사용량은 ai_token_usages 테이블 기반 +✅ 저장공간은 tenant.storage_used/limit 기반 +❌ 하드코딩된 상한값 제거 +❌ 별도 구독관리 메뉴 유지 (통합) +``` + +--- + +## 2. 현재 상태 분석 + +### 2.1 기존 데이터 흐름 + +``` +React (/subscription) + ↓ +GET /api/v1/subscriptions/current → subscriptions 테이블 (실제 데이터) +GET /api/v1/subscriptions/usage → 혼합 (실제 + 하드코딩) + ↓ +SubscriptionManagement 컴포넌트 렌더링 +``` + +### 2.2 현재 usage API 반환 항목 + +| 항목 | 데이터 소스 | 실제/Mock | 문제점 | +|------|-----------|:---------:|--------| +| 사용자 수 | `tenant.users.count()` / `tenant.max_users` | 실제 | `max_users` 미설정 시 0 | +| 저장공간 | `tenant.storage_used` / `tenant.storage_limit` | 실제 | 파일 업로드 시 자동 갱신됨 | +| API 호출 수 | `ApiRequestLog` count (일간) / 10,000 | **반만 실제** | 상한 10,000 하드코딩, 1일만 보관 | +| 구독 정보 | `subscriptions` 테이블 | 실제 | plan 연결 구조만 존재 | + +### 2.3 AI 토큰 추적 현황 + +| 항목 | 상태 | 위치 | +|------|------|------| +| `ai_token_usages` 테이블 | ✅ 존재 | API 마이그레이션 | +| AI 호출 시 기록 | ✅ 동작 중 | MNG에서 AI 기능 사용 시 자동 기록 | +| 토큰별 비용 계산 | ✅ 동작 중 | `ai_pricing_configs` 테이블 기반 | +| **서비스(React) API 제공** | ❌ 없음 | API 프로젝트에 엔드포인트 미구현 | + +### 2.4 기존 테이블 구조 + +``` +tenants +├── storage_used (bigint, bytes) ← 실제 추적 중 +├── storage_limit (bigint, bytes) ← 기본 10GB +├── max_users (int, nullable) +│ +├── subscriptions (1:N) +│ ├── plan (varchar 50, 'Business' 등) +│ ├── monthly_fee (bigint) +│ ├── start_date / next_billing +│ └── status (active/cancelled/...) +│ +└── ai_token_usages (1:N) + ├── model (varchar, 'gemini-2.0-flash' 등) + ├── menu_name (varchar, 기능명) + ├── prompt_tokens / completion_tokens / total_tokens + ├── cost_usd / cost_krw + └── created_at + +ai_pricing_configs +├── provider (gemini/claude/google-stt/google-gcs) +├── model_name +├── input_price_per_million / output_price_per_million +└── exchange_rate +``` + +--- + +## 3. 목표 설계 + +### 3.1 통합 페이지 구성 + +``` +이용현황 (/usage) +┌─────────────────────────────────────────────────┐ +│ │ +│ [1] 구독 정보 카드 │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 요금제 │ │ 구독상태 │ │ 다음결제 │ │ +│ │ Business │ │ 활성 │ │ 04-01 │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ [2] 리소스 사용량 │ +│ ┌──────────────────────────────────────┐ │ +│ │ 사용자 ████████░░ 5 / 10명 │ │ +│ │ 저장공간 ███░░░░░░░ 1.2GB / 10GB │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ [3] AI 토큰 사용량 (이번 달) │ +│ ┌──────────────────────────────────────┐ │ +│ │ 총 토큰 12,345 tokens │ │ +│ │ 총 비용 ₩1,234 (입력/출력 구분) │ │ +│ │ 모델별: Gemini 80% | Claude 20% │ │ +│ │ [상세 보기 →] │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ [4] 서비스 관리 버튼 │ +│ [ 자료 내보내기 ] [ 서비스 해지 ] │ +│ │ +└─────────────────────────────────────────────────┘ +``` + +### 3.2 통합 API 설계 + +기존 `/subscriptions/usage` API를 확장하여 AI 토큰 정보를 포함한다. + +#### `GET /api/v1/subscriptions/usage` (개선) + +```json +{ + "users": { + "used": 5, + "limit": 10, + "percentage": 50.0 + }, + "storage": { + "used": 1288490189, + "used_formatted": "1.2 GB", + "limit": 10737418240, + "limit_formatted": "10 GB", + "percentage": 12.0 + }, + "ai_tokens": { + "period": "2026-03", + "total_requests": 156, + "total_tokens": 12345, + "prompt_tokens": 8200, + "completion_tokens": 4145, + "cost_usd": 0.95, + "cost_krw": 1234, + "by_model": [ + { + "model": "gemini-2.0-flash", + "requests": 120, + "total_tokens": 9800, + "cost_krw": 987 + }, + { + "model": "claude-3-haiku", + "requests": 36, + "total_tokens": 2545, + "cost_krw": 247 + } + ] + }, + "subscription": { + "plan": "Business", + "monthly_fee": 50000, + "status": "active", + "start_date": "2026-01-01", + "next_billing": "2026-04-01", + "remaining_days": 14 + } +} +``` + +**변경점**: +- `api_calls` 섹션 → `ai_tokens` 섹션으로 교체 +- `ai_tokens`에 월별 토큰 합계, 비용, 모델별 내역 포함 +- `subscription`에 `monthly_fee` 추가 +- 하드코딩 제거 (10,000 상한 등) + +--- + +## 4. 작업 범위 + +### 4.1 API 프로젝트 (`sam/api`) + +| 작업 | 파일 | 설명 | +|------|------|------| +| R1 | `SubscriptionService.php` | `usage()` 메서드에 AI 토큰 집계 추가 | +| R2 | `SubscriptionService.php` | `api_calls` 섹션을 `ai_tokens`로 교체 | +| R3 | `SubscriptionService.php` | `subscription` 섹션에 `monthly_fee` 추가 | +| R4 | `SubscriptionController.php` | 변경 없음 (Service만 수정) | + +#### R1 상세: AI 토큰 집계 쿼리 + +```php +// SubscriptionService::usage() 내부 +$currentMonth = now()->format('Y-m'); + +$aiUsage = 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(); + +$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(); +``` + +### 4.2 React 프로젝트 (`sam/react`) + +| 작업 | 파일 | 설명 | +|------|------|------| +| R5 | `types.ts` | `UsageApiData` 타입에 `ai_tokens` 추가 | +| R6 | `actions.ts` | 기존 `getUsage()` 유지 (API 응답 구조만 변경) | +| R7 | `SubscriptionManagement.tsx` | AI 토큰 섹션 UI 추가 | +| R8 | `utils.ts` | `transformApiToFrontend()` AI 토큰 변환 추가 | +| R9 | `/usage/page.tsx` 생성 | 새 페이지 (SubscriptionManagement 재사용) | +| R10 | `/subscription/page.tsx` | `/usage`로 리다이렉트 처리 | + +### 4.3 메뉴 변경 + +| 작업 | 대상 | 설명 | +|------|------|------| +| R11 | `menus` 테이블 | 기존 `구독관리`(`/subscription`) 메뉴 → URL을 `/usage`로 변경하거나 비활성화 | +| R12 | `menus` 테이블 | `이용현황`(`/usage`) 메뉴 유지 | + +> **결과**: 사이드바에 `이용현황` 하나만 표시, 클릭 시 통합 페이지 + +--- + +## 5. 제거 대상 + +| 항목 | 사유 | +|------|------| +| `api_calls` 섹션 (usage API) | 일반 API 호출 횟수는 무의미 → AI 토큰으로 대체 | +| 하드코딩 `apiCallsLimit: 10000` | 실제 제한이 아닌 의미없는 수치 | +| `ApiRequestLog` 기반 사용량 | 1일만 보관, 통계 목적 부적합 | +| `/subscription` 별도 메뉴 | `/usage`로 통합 | + +--- + +## 6. 작업 순서 + +``` +Phase 1: API 개선 (api) + └── R1~R3: usage() 메서드 수정 (AI 토큰 집계 추가) + +Phase 2: React 페이지 통합 (react) + └── R5~R8: 타입/컴포넌트 수정 (AI 토큰 UI 추가) + └── R9~R10: /usage 페이지 생성, /subscription 리다이렉트 + +Phase 3: 메뉴 정리 + └── R11~R12: 메뉴 통합 (tinker로 직접 수정) +``` + +--- + +## 7. 확인 필요 사항 + +작업 시작 전 확인이 필요한 항목: + +| # | 질문 | 영향 | +|---|------|------| +| Q1 | AI 토큰 사용량에 월별 한도가 필요한가? (무제한 vs 플랜별 상한) | `ai_tokens.limit` 필드 추가 여부 | +| Q2 | AI 토큰 비용을 고객에게 보여줘도 되는가? (원화 금액 노출) | UI 표시 항목 결정 | +| Q3 | 이용현황 페이지 접근 권한은? (관리자만 vs 모든 사용자) | 권한 설정 | +| Q4 | 저장공간의 실제 사용량이 정확히 갱신되고 있는가? | 파일 업로드/삭제 시 `incrementStorage`/`decrementStorage` 호출 여부 | + +--- + +## 관련 문서 + +| 문서 | 경로 | +|------|------| +| API 개발 규칙 | `dev/standards/api-rules.md` | +| options 정책 | `standards/options-column-policy.md` | + +--- + +**최종 업데이트**: 2026-03-18