Files
sam-docs/features/settlement/subscriptions.md
김보곤 8faf5afa8b docs:정산관리 개발문서 추가 (4개 메뉴)
- 영업수수료정산: 입금등록, 수당자동계산, 승인프로세스, 지급추적
- 컨설팅비용정산: 상담시간×시급 정산, 상태관리
- 고객사정산: 월별 매출/수수료/비용 순정산액 관리
- 구독료정산: 플랜별 과금, MRR/ARR 집계, 구독 라이프사이클

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:47:22 +09:00

261 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 구독료정산
## 개요
구독료정산은 고객사별 구독 플랜과 과금을 관리하는 기능입니다.
플랜별 월정액 관리, 과금주기(월/연), MRR/ARR 집계, 사용자 수 추적을 지원합니다.
- **라우트**: `GET /finance/subscription`
- **UI 기술**: React 18 + Babel (브라우저 트랜스파일링)
## 파일 구조
```
mng/
├── app/Http/Controllers/Finance/
│ └── SubscriptionController.php # MNG 컨트롤러 (4개 메서드)
├── app/Models/Finance/
│ └── Subscription.php # MNG 구독 모델
└── resources/views/finance/
└── subscription.blade.php # React 기반 단일 페이지
api/
├── app/Http/Controllers/Api/V1/
│ └── SubscriptionController.php # API 컨트롤러 (REST)
├── app/Services/
│ └── SubscriptionService.php # 비즈니스 로직 서비스
├── app/Models/Tenants/
│ └── Subscription.php # API 구독 모델 (상세 구현)
└── database/migrations/
├── 2026_02_04_230008_create_subscriptions_table.php
└── 2025_12_22_222722_add_cancel_columns_to_subscriptions_table.php
```
## 라우트
### MNG 라우트
```php
// routes/web.php (finance prefix 그룹 내)
GET /subscription Blade 페이지 렌더링 (HX-Redirect)
// API 라우트 (subscriptions prefix)
GET /subscriptions/list index() 목록 + 통계 (JSON)
POST /subscriptions/store store() 등록
PUT /subscriptions/{id} update() 수정
DELETE /subscriptions/{id} destroy() 삭제
```
### API 라우트
```php
// routes/api.php (v1/subscriptions prefix)
GET / index() 목록 (페이지네이션)
GET /{id} show() 상세 조회
POST / store() 생성
PUT /{id} update() 수정
DELETE /{id} destroy() 삭제
POST /{id}/cancel cancel() 구독 취소
POST /{id}/suspend suspend() 일시정지
POST /{id}/resume resume() 재개
```
## 컨트롤러
### SubscriptionController (MNG)
| 메서드 | HTTP | 설명 |
|--------|------|------|
| `index()` | GET | 목록 + MRR/ARR 통계 (검색, 상태 필터) |
| `store()` | POST | 구독 등록 |
| `update()` | PUT | 구독 수정 |
| `destroy()` | DELETE | 삭제 (Soft Delete) |
### index() 응답 구조
```json
{
"success": true,
"data": [
{
"id": 1,
"customer": "A사",
"plan": "Business",
"monthlyFee": 500000,
"billingCycle": "monthly",
"startDate": "2026-01-01",
"nextBilling": "2026-03-01",
"status": "active",
"users": 10,
"memo": ""
}
],
"stats": {
"activeCount": 15,
"monthlyRecurring": 7500000,
"yearlyRecurring": 90000000,
"totalUsers": 150
}
}
```
## 핵심 로직
### MRR/ARR 계산
```
MRR (Monthly Recurring Revenue)
= SUM(active 구독의 monthly_fee) (billingCycle=monthly)
+ SUM(active 구독의 monthly_fee / 12) (billingCycle=yearly를 월 환산)
ARR (Annual Recurring Revenue)
= MRR × 12
```
### 구독 상태 관리 (API 모델)
```
pending (대기)
↓ activate()
active (활성)
├── cancel(reason) → cancelled (취소)
├── suspend() → suspended (일시정지)
│ ↓ resume() → active (재활성)
└── (만료) → expired
```
### 구독 플랜
| 플랜 | 설명 |
|------|------|
| Starter | 소규모 기본 플랜 |
| Business | 중규모 비즈니스 플랜 |
| Enterprise | 대규모 기업 플랜 |
### 과금 주기
| 주기 | 설명 |
|------|------|
| monthly | 월간 과금 |
| yearly | 연간 과금 |
## 모델
### Subscription (MNG)
**테이블**: `subscriptions`
| 필드 | 타입 | 설명 |
|------|------|------|
| `tenant_id` | bigint | 테넌트 ID |
| `customer` | string | 고객사명 |
| `plan` | string | 플랜명 (Starter/Business/Enterprise) |
| `monthly_fee` | int | 월 정액 |
| `billing_cycle` | string | monthly / yearly |
| `start_date` | date | 시작일 |
| `next_billing` | date | 다음 과금일 |
| `status` | string | active / trial / cancelled |
| `users` | int | 사용자 수 |
| `memo` | text | 메모 |
| `cancelled_at` | timestamp | 취소 일시 |
| `cancel_reason` | string(500) | 취소 사유 |
- SoftDeletes 적용
- Casts: monthly_fee, users → integer, start_date, next_billing → date
- Scope: `forTenant($tenantId)`
### Subscription (API - 상세 구현)
API 프로젝트의 모델은 더 상세한 구독 라이프사이클을 지원합니다:
#### 상태 상수
```php
const STATUS_ACTIVE = 'active';
const STATUS_CANCELLED = 'cancelled';
const STATUS_EXPIRED = 'expired';
const STATUS_PENDING = 'pending';
const STATUS_SUSPENDED = 'suspended';
```
#### Relationships
```php
$subscription->tenant // BelongsTo Tenant
$subscription->plan // BelongsTo Plan
$subscription->payments // HasMany Payment
```
#### 주요 Accessor
```php
$subscription->statusLabel // 상태 한글 라벨
$subscription->isExpired // 만료 여부
$subscription->remainingDays // 잔여 일수
$subscription->isValid // 유효 여부 (active + 미만료)
$subscription->totalPaid // 총 결제 금액
```
#### 주요 메서드
```php
$subscription->activate() // 활성화
$subscription->renew($newEndDate) // 갱신
$subscription->cancel($reason) // 취소
$subscription->suspend() // 일시정지
$subscription->resume() // 재개
$subscription->isCancellable() // 취소 가능 여부
```
## 서비스 클래스 (API)
### SubscriptionService
| 메서드 | 설명 |
|--------|------|
| `index(params)` | 페이지네이션 목록 (필터, 정렬) |
| `current()` | 현재 활성 구독 조회 |
| `show(id)` | 상세 (플랜, 최근 결제 포함) |
| `store(data)` | 구독 생성 (결제 처리) |
| `update(id, data)` | 구독 수정 |
| `cancel(id, reason)` | 구독 취소 |
| `suspend(id)` | 일시정지 |
| `resume(id)` | 재개 |
| `renew(id, newEndDate)` | 갱신 |
## 뷰 구성 (React)
### subscription.blade.php
```
┌─ 페이지 헤더 ──────────────────────
│ 제목: "구독료정산"
│ [CSV 내보내기] [등록] 버튼
├─ 통계 카드 (4열) ──────────────────
│ 활성 구독 | MRR (월간 반복 수익) | ARR (연간 반복 수익) | 총 사용자
├─ 필터 영역 ────────────────────────
│ 검색 (고객사명) | 플랜 (Starter/Business/Enterprise) | 상태 (active/trial/cancelled)
├─ 구독 목록 테이블 ─────────────────
│ 고객사 | 플랜 | 월정액 | 과금주기 | 다음과금일 | 사용자 | 상태 | 작업
│ └─ 플랜: Starter(회색), Business(파랑), Enterprise(보라) 배지
│ └─ 상태: active(초록), trial(파랑), cancelled(빨강) 배지
│ └─ 작업: 수정/삭제 버튼
├─ 등록/수정 모달 ───────────────────
│ 고객사명, 플랜 선택
│ 월정액, 과금주기(월/연)
│ 시작일, 다음 과금일
│ 상태, 사용자 수, 메모
│ [삭제] [취소] [등록/저장]
└─ 비어있을 때: 안내 메시지
```
## HTMX 호환성
- React 기반 페이지이므로 **HX-Redirect 필요**
- `@push('scripts')` 블록에 React/Babel 스크립트 포함