diff --git a/features/settlement/README.md b/features/settlement/README.md new file mode 100644 index 0000000..bb7ff8f --- /dev/null +++ b/features/settlement/README.md @@ -0,0 +1,102 @@ +# 정산관리 기능 + +## 개요 + +SAM 프로젝트의 정산관리 모듈은 영업수수료, 컨설팅비용, 고객사별 정산, 구독료 등 +다양한 정산 업무를 종합적으로 관리하는 시스템입니다. +입금 등록, 수당 자동 계산, 승인 프로세스, 지급 추적, 통계 기능을 제공합니다. + +## 메뉴 구성 + +| 메뉴 | 경로 | 설명 | UI 기술 | +|------|------|------|---------| +| [영업수수료정산](./sales-commissions.md) | `/finance/sales-commissions` | 영업파트너/매니저 수당 정산 | Blade + HTMX | +| [컨설팅비용정산](./consulting-fees.md) | `/finance/consulting-fee` | 컨설턴트별 상담수수료 관리 | React 18 | +| [고객사정산](./customer-settlements.md) | `/finance/customer-settlement` | 고객사별 매출/비용 정산 | React 18 | +| [구독료정산](./subscriptions.md) | `/finance/subscription` | 구독 플랜별 과금 관리 | React 18 | + +## 아키텍처 + +``` +┌───────────────────────────────────────────────────────────────┐ +│ 정산관리 모듈 │ +├───────────────┬───────────────┬───────────────┬───────────────┤ +│ 영업수수료정산 │ 컨설팅비용정산 │ 고객사정산 │ 구독료정산 │ +│ (Blade+HTMX) │ (React 18) │ (React 18) │ (React 18) │ +│ │ │ │ │ +│ 입금등록 │ 상담기록 │ 월별정산 │ 플랜관리 │ +│ 수당자동계산 │ 시급계산 │ 매출/비용 │ MRR/ARR │ +│ 승인프로세스 │ 상태관리 │ 순정산액 │ 과금주기 │ +│ 지급추적 │ │ │ 사용자수 │ +└───────┬───────┴───────┬───────┴───────┬───────┴───────┬───────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌───────────────────────────────────────────────────────────────┐ +│ 데이터베이스 │ +│ sales_commissions, sales_commission_details, │ +│ consulting_fees, customer_settlements, subscriptions │ +└───────────────────────────────────────────────────────────────┘ +``` + +## 주요 기술 스택 + +| 기술 | 용도 | +|------|------| +| Laravel 11 (PHP 8.3) | 백엔드 프레임워크 | +| Blade + HTMX | 영업수수료정산 UI (부분 업데이트, 모달) | +| React 18 + Babel | 컨설팅/고객사/구독료 UI (브라우저 트랜스파일링) | +| Tailwind CSS + Lucide | 스타일링 및 아이콘 | +| MySQL 8.0 | 데이터 저장 | + +## 데이터 흐름 + +``` +영업수수료정산: + sales_tenant_managements (계약) → 입금 등록 + ↓ + sales_commissions (수당 자동 계산) + ├── sales_commission_details (상품별 상세) + └── 승인 → 지급완료 프로세스 + +컨설팅비용정산: + consulting_fees (상담시간 × 시급 = 정산액) + +고객사정산: + customer_settlements (매출 - 수수료 - 비용 = 순정산액) + +구독료정산: + subscriptions (플랜별 월정액 × 과금주기) +``` + +## 공통 패턴 + +### 멀티 테넌트 + +모든 테이블은 `tenant_id` 기반으로 데이터를 격리합니다. +컨트롤러에서 `session('selected_tenant_id', 1)`로 현재 테넌트를 결정합니다. + +### 상태 관리 + +| 기능 | 상태값 | +|------|--------| +| 영업수수료 | pending → approved → paid / cancelled | +| 컨설팅비용 | pending / paid | +| 고객사정산 | pending / settled | +| 구독료 | active / trial / cancelled / expired / suspended | + +### CSV 내보내기 + +모든 기능에서 CSV 내보내기를 지원합니다: +- 영업수수료: 서버 사이드 (UTF-8 BOM 인코딩) +- 컨설팅/고객사/구독: 클라이언트 사이드 (React) + +## 데이터베이스 테이블 요약 + +| 테이블 | 역할 | +|--------|------| +| `sales_commissions` | 영업수수료 정산 (입금, 수당, 승인, 지급) | +| `sales_commission_details` | 상품별 수당 상세 내역 | +| `consulting_fees` | 컨설팅 상담수수료 기록 | +| `customer_settlements` | 고객사별 월간 정산 | +| `subscriptions` | 구독 플랜 및 과금 관리 | +| `customers` | 고객사 마스터 (참조) | diff --git a/features/settlement/consulting-fees.md b/features/settlement/consulting-fees.md new file mode 100644 index 0000000..c541541 --- /dev/null +++ b/features/settlement/consulting-fees.md @@ -0,0 +1,148 @@ +# 컨설팅비용정산 + +## 개요 + +컨설팅비용정산은 컨설턴트별 상담수수료를 기록하고 관리하는 기능입니다. +상담 시간과 시급 기반의 정산액 계산, 지급 상태 관리, 통계 기능을 제공합니다. + +- **라우트**: `GET /finance/consulting-fee` +- **UI 기술**: React 18 + Babel (브라우저 트랜스파일링) + +## 파일 구조 + +``` +mng/ +├── app/Http/Controllers/Finance/ +│ └── ConsultingFeeController.php # 메인 컨트롤러 (4개 메서드) +├── app/Models/Finance/ +│ └── ConsultingFee.php # 상담수수료 모델 +└── resources/views/finance/ + └── consulting-fee.blade.php # React 기반 단일 페이지 + +api/ +└── database/migrations/ + └── 2026_02_04_230006_create_consulting_fees_table.php +``` + +## 라우트 + +```php +// routes/web.php (finance prefix 그룹 내) +GET /consulting-fee → Blade 페이지 렌더링 (HX-Redirect) + +// API 라우트 (consulting-fees prefix) +GET /consulting-fees/list → index() 목록 + 통계 (JSON) +POST /consulting-fees/store → store() 등록 +PUT /consulting-fees/{id} → update() 수정 +DELETE /consulting-fees/{id} → destroy() 삭제 +``` + +## 컨트롤러 + +### ConsultingFeeController + +| 메서드 | HTTP | 설명 | +|--------|------|------| +| `index()` | GET | 목록 + 통계 (검색, 상태 필터) | +| `store()` | POST | 상담수수료 등록 | +| `update()` | PUT | 수정 | +| `destroy()` | DELETE | 삭제 (Soft Delete) | + +### index() 응답 구조 + +```json +{ + "success": true, + "data": [ + { + "id": 1, + "date": "2026-02-10", + "consultant": "김컨설턴트", + "customer": "A사", + "service": "기술 컨설팅", + "hours": 4, + "hourlyRate": 150000, + "amount": 600000, + "status": "pending", + "memo": "" + } + ], + "stats": { + "totalAmount": 3500000, + "paidAmount": 2000000, + "pendingAmount": 1500000, + "totalHours": 28 + } +} +``` + +## 핵심 로직 + +### 정산액 계산 + +``` +정산액(amount) = 상담시간(hours) × 시급(hourly_rate) +``` + +### 상태 관리 + +``` +pending (대기) → paid (지급완료) +``` + +## 모델 + +### ConsultingFee + +**테이블**: `consulting_fees` + +| 필드 | 타입 | 설명 | +|------|------|------| +| `tenant_id` | bigint | 테넌트 ID | +| `date` | date | 상담일 | +| `consultant` | string(50) | 컨설턴트명 | +| `customer` | string(100) | 고객사명 | +| `service` | string(50) | 서비스 유형 (기본: '기술 컨설팅') | +| `hours` | int | 상담 시간 | +| `hourly_rate` | int | 시급 | +| `amount` | bigint | 정산액 | +| `status` | string(20) | pending / paid | +| `memo` | text | 메모 | + +- SoftDeletes 적용 +- Scope: `forTenant($tenantId)` + +## 뷰 구성 (React) + +### consulting-fee.blade.php + +``` +┌─ 페이지 헤더 ────────────────────── +│ 제목: "컨설팅비용정산" +│ [CSV 내보내기] [등록] 버튼 +│ +├─ 통계 카드 (4열) ────────────────── +│ 총 상담시간 | 총 정산액 | 지급완료 | 미지급 +│ +├─ 필터 영역 ──────────────────────── +│ 검색 (컨설턴트/고객사) | 컨설턴트 필터 | 상태 필터 +│ 기간 선택 (시작일 ~ 종료일) +│ +├─ 정산 목록 테이블 ───────────────── +│ 날짜 | 컨설턴트 | 고객사 | 서비스 | 시간 | 시급 | 정산액 | 상태 | 작업 +│ └─ 상태: 대기(노랑), 지급완료(초록) 배지 +│ └─ 작업: 수정/삭제 버튼 +│ +├─ 등록/수정 모달 ─────────────────── +│ 상담일, 컨설턴트, 고객사, 서비스 유형 +│ 상담시간, 시급, 정산액 (자동 계산) +│ 상태, 메모 +│ [삭제] [취소] [등록/저장] +│ +└─ 비어있을 때: 안내 메시지 +``` + +## HTMX 호환성 + +- React 기반 페이지이므로 **HX-Redirect 필요** +- `@push('scripts')` 블록에 React/Babel 스크립트 포함 diff --git a/features/settlement/customer-settlements.md b/features/settlement/customer-settlements.md new file mode 100644 index 0000000..8e8f8f5 --- /dev/null +++ b/features/settlement/customer-settlements.md @@ -0,0 +1,165 @@ +# 고객사정산 + +## 개요 + +고객사정산은 고객사별 월간 매출, 수수료, 비용을 집계하여 순정산액을 관리하는 기능입니다. +기간별 정산 현황 추적, 정산 완료 처리, 통계 기능을 제공합니다. + +- **라우트**: `GET /finance/customer-settlement` +- **UI 기술**: React 18 + Babel (브라우저 트랜스파일링) + +## 파일 구조 + +``` +mng/ +├── app/Http/Controllers/Finance/ +│ └── CustomerSettlementController.php # 메인 컨트롤러 (4개 메서드) +├── app/Models/Finance/ +│ └── CustomerSettlement.php # 고객사정산 모델 +└── resources/views/finance/ + └── customer-settlement.blade.php # React 기반 단일 페이지 + +api/ +└── database/migrations/ + └── 2026_02_04_230007_create_customer_settlements_table.php +``` + +## 라우트 + +```php +// routes/web.php (finance prefix 그룹 내) +GET /customer-settlement → Blade 페이지 렌더링 (HX-Redirect) + +// API 라우트 (customer-settlements prefix) +GET /customer-settlements/list → index() 목록 + 통계 (JSON) +POST /customer-settlements/store → store() 등록 +PUT /customer-settlements/{id} → update() 수정 +DELETE /customer-settlements/{id} → destroy() 삭제 +``` + +## 컨트롤러 + +### CustomerSettlementController + +| 메서드 | HTTP | 설명 | +|--------|------|------| +| `index()` | GET | 목록 + 통계 (검색, 기간, 상태 필터) | +| `store()` | POST | 정산 등록 | +| `update()` | PUT | 정산 수정 | +| `destroy()` | DELETE | 삭제 (Soft Delete) | + +### index() 응답 구조 + +```json +{ + "success": true, + "data": [ + { + "id": 1, + "period": "2026-02", + "customer": "A사", + "totalSales": 10000000, + "commission": 500000, + "expense": 200000, + "netAmount": 9300000, + "status": "pending", + "settledDate": null, + "memo": "" + } + ], + "stats": { + "totalSales": 50000000, + "totalCommission": 2500000, + "totalNet": 45000000, + "settledAmount": 30000000 + } +} +``` + +## 핵심 로직 + +### 순정산액 계산 + +``` +순정산액(net_amount) = 총 매출(total_sales) - 수수료(commission) - 비용(expense) +``` + +### 상태 관리 + +``` +pending (대기) → settled (정산완료) +→ 정산완료 시 settled_date 기록 +``` + +## 모델 + +### CustomerSettlement + +**테이블**: `customer_settlements` + +| 필드 | 타입 | 설명 | +|------|------|------| +| `tenant_id` | bigint | 테넌트 ID | +| `period` | string(7) | 정산월 (YYYY-MM) | +| `customer` | string(100) | 고객사명 | +| `total_sales` | bigint | 총 매출액 | +| `commission` | bigint | 수수료 | +| `expense` | bigint | 비용 | +| `net_amount` | bigint | 순정산액 | +| `status` | string(20) | pending / settled | +| `settled_date` | date | 정산완료일 | +| `memo` | text | 메모 | + +- SoftDeletes 적용 +- Casts: total_sales, commission, expense, net_amount → integer, settled_date → date +- Scope: `forTenant($tenantId)` + +## 뷰 구성 (React) + +### customer-settlement.blade.php + +``` +┌─ 페이지 헤더 ────────────────────── +│ 제목: "고객사정산" +│ [CSV 내보내기] [등록] 버튼 +│ +├─ 통계 카드 (4열) ────────────────── +│ 총 매출 | 정산금액 | 정산완료 | 총 수수료 +│ +├─ 필터 영역 ──────────────────────── +│ 검색 (고객사명) | 기간 (YYYY-MM) | 상태 (전체/대기/정산완료) +│ +├─ 정산 목록 테이블 ───────────────── +│ 기간 | 고객사 | 매출 | 수수료 | 비용 | 정산액 | 상태 | 작업 +│ └─ 정산액: 매출-수수료-비용 자동 계산 +│ └─ 상태: 대기(노랑), 정산완료(초록) 배지 +│ └─ 작업: 수정/삭제 버튼 +│ +├─ 등록/수정 모달 ─────────────────── +│ 정산월(YYYY-MM), 고객사명 +│ 총 매출액, 수수료, 비용 +│ → 순정산액 (자동 계산 표시) +│ 상태, 정산완료일, 메모 +│ [삭제] [취소] [등록/저장] +│ +└─ 비어있을 때: 안내 메시지 +``` + +## 데이터 흐름 + +``` +React 컴포넌트 + ↓ fetch GET /finance/customer-settlements/list +CustomerSettlementController::index() + ↓ +CustomerSettlement::forTenant() + ↓ (search, status, period 필터) +통계 계산 (totalSales, totalCommission, totalNet, settledAmount) + ↓ +JSON 응답 → React 렌더링 +``` + +## HTMX 호환성 + +- React 기반 페이지이므로 **HX-Redirect 필요** +- `@push('scripts')` 블록에 React/Babel 스크립트 포함 diff --git a/features/settlement/sales-commissions.md b/features/settlement/sales-commissions.md new file mode 100644 index 0000000..94c6a0c --- /dev/null +++ b/features/settlement/sales-commissions.md @@ -0,0 +1,264 @@ +# 영업수수료정산 + +## 개요 + +영업수수료정산은 영업파트너와 매니저의 수당을 계산하고 승인/지급하는 기능입니다. +입금 등록 시 수당 자동 계산, 승인 프로세스(대기→승인→지급), 지급 추적, 일괄 처리를 지원합니다. + +- **라우트**: `GET /finance/sales-commissions` +- **UI 기술**: Blade + HTMX (부분 업데이트, 모달) +- **컨트롤러**: `SalesCommissionController` (13개 메서드) +- **서비스**: `SalesCommissionService` (핵심 비즈니스 로직) + +## 파일 구조 + +``` +mng/ +├── app/Http/Controllers/Finance/ +│ └── SalesCommissionController.php # 메인 컨트롤러 (13개 메서드) +├── app/Services/ +│ └── SalesCommissionService.php # 비즈니스 로직 서비스 +├── app/Models/Sales/ +│ ├── SalesCommission.php # 수수료 정산 모델 +│ └── SalesCommissionDetail.php # 상품별 상세 모델 +└── resources/views/finance/sales-commission/ + ├── index.blade.php # 메인 페이지 + ├── stats-cards.blade.php # 통계 카드 (HTMX partial) + ├── commission-table.blade.php # 테이블 (HTMX partial) + └── payment-form.blade.php # 입금 등록 폼 (HTMX partial) + +api/ +└── database/migrations/ + ├── 2026_01_29_170000_create_sales_commissions_table.php + └── 2026_01_29_170100_create_sales_commission_details_table.php +``` + +## 라우트 + +```php +// routes/web.php (finance prefix 그룹 내) +GET /sales-commissions → index() 목록 페이지 +POST /sales-commissions/payment → registerPayment() 입금 등록 +GET /sales-commissions/{id} → show() JSON 상세 +GET /sales-commissions/{id}/detail → detail() 모달 상세 (HTMX) +POST /sales-commissions/{id}/approve → approve() 승인 +POST /sales-commissions/{id}/mark-paid → markPaid() 지급완료 +POST /sales-commissions/{id}/cancel → cancel() 취소 +POST /sales-commissions/bulk-approve → bulkApprove() 일괄 승인 +POST /sales-commissions/bulk-mark-paid → bulkMarkPaid() 일괄 지급완료 +GET /sales-commissions/table → table() 테이블 새로고침 (HTMX) +GET /sales-commissions/stats → stats() 통계 새로고침 (HTMX) +GET /sales-commissions/payment-form → paymentForm() 입금 폼 (HTMX) +GET /sales-commissions/export → export() CSV 다운로드 +``` + +## 컨트롤러 + +### SalesCommissionController + +| 메서드 | HTTP | 설명 | +|--------|------|------| +| `index()` | GET | 정산 목록 (필터, 페이지네이션) | +| `show(id)` | GET | JSON 상세 조회 | +| `detail(id)` | GET | 모달 상세 (HTMX partial) | +| `registerPayment()` | POST | **입금 등록 및 수당 자동 생성** | +| `approve(id)` | POST | 단일 승인 처리 | +| `bulkApprove()` | POST | 일괄 승인 (체크박스 선택) | +| `markPaid(id)` | POST | 단일 지급완료 | +| `bulkMarkPaid()` | POST | 일괄 지급완료 | +| `cancel(id)` | POST | 취소 처리 (paid 상태 제외) | +| `table()` | GET | 테이블 부분 새로고침 (HTMX) | +| `stats()` | GET | 통계 카드 부분 새로고침 (HTMX) | +| `paymentForm()` | GET | 입금 등록 폼 모달 (HTMX) | +| `export()` | GET | CSV 엑셀 다운로드 (UTF-8 BOM) | + +## 서비스 클래스 + +### SalesCommissionService + +#### 상수 + +```php +const DEFAULT_PARTNER_RATE = 20.00; // 기본 파트너 수당률 (%) +const DEFAULT_MANAGER_RATE = 5.00; // 기본 매니저 수당률 (%) +``` + +#### 주요 메서드 + +| 메서드 | 설명 | +|--------|------| +| `getCommissions(filters, perPage)` | 정산 목록 (페이지네이션, 필터) | +| `getCommissionById(id)` | 정산 상세 (관계 포함) | +| `getPendingPaymentTenants()` | 입금 대기 테넌트 목록 | +| `createCommission(managementId, type, amount, date)` | **입금 등록 + 수당 생성** | +| `approve(id, approverId)` | 승인 처리 | +| `bulkApprove(ids, approverId)` | 일괄 승인 | +| `markAsPaid(id, bankReference?)` | 지급완료 처리 | +| `bulkMarkAsPaid(ids, bankReference?)` | 일괄 지급완료 | +| `cancel(id)` | 취소 처리 | +| `getSettlementStats(year, month)` | 월별 정산 통계 | +| `getPartnerCommissionSummary(partnerId)` | 파트너 수당 요약 | +| `getManagerCommissionSummary(userId)` | 매니저 수당 요약 | + +## 핵심 로직 + +### 수당 자동 계산 (createCommission) + +``` +입금 등록 + ↓ +1. 계약 상품 목록 조회 (sales_contract_products) + ↓ +2. 각 상품별 수당 계산: + 기본 가입비 = 등록 금액 × 2 (없을 경우) + 기준액 = 가입비 × 50% + 파트너 수당 = 기준액 × 20% + 매니저 수당 = 기준액 × 5% (매니저 있을 경우만) + ↓ +3. sales_commissions 레코드 생성 + + sales_commission_details (상품별 N건) + ↓ +4. sales_tenant_managements 입금 정보 업데이트 + ↓ +5. DB 트랜잭션으로 원자적 처리 +``` + +### 승인 프로세스 + +``` +pending (대기) + ↓ approve() +approved (승인) ← approved_by, approved_at 기록 + ↓ markAsPaid() +paid (지급완료) ← actual_payment_date, bank_reference 기록 + +pending/approved → cancel() → cancelled +(paid 상태에서는 취소 불가) +``` + +### 지급 추적 (SalesCommission 모델) + +``` +1차 납입완료일 → first_payment_date +1차 파트너 수당 지급일 → first_partner_paid_date +2차 납입완료일 → second_payment_date +2차 파트너 수당 지급일 → second_partner_paid_date +첫 구독료 입금일 → first_subscription_date +매니저 수당 지급일 → manager_paid_date +``` + +### 지급예정일 계산 + +``` +1차 파트너 수당: 1차 납입완료 익월 10일 +2차 파트너 수당: 2차 납입완료 익월 10일 +매니저 수당: 첫 구독료 입금 익월 10일 +``` + +## 모델 + +### SalesCommission + +**테이블**: `sales_commissions` + +| 필드 | 타입 | 설명 | +|------|------|------| +| `tenant_id` | bigint | 테넌트 ID | +| `management_id` | bigint | 영업 테넌트 관리 FK | +| `payment_type` | enum | deposit(계약금) / balance(잔금) | +| `payment_amount` | decimal(14,2) | 입금액 | +| `payment_date` | date | 입금일 | +| `base_amount` | decimal(14,2) | 수당 계산 기준액 (가입비 50%) | +| `partner_rate` | decimal(5,2) | 파트너 수당률 (기본 20%) | +| `manager_rate` | decimal(5,2) | 매니저 수당률 (기본 5%) | +| `partner_commission` | decimal(14,2) | 파트너 수당액 | +| `manager_commission` | decimal(14,2) | 매니저 수당액 | +| `scheduled_payment_date` | date | 지급예정일 (입금 익월 10일) | +| `status` | enum | pending / approved / paid / cancelled | +| `actual_payment_date` | date | 실제 지급일 | +| `partner_id` | bigint | 영업파트너 FK | +| `manager_user_id` | bigint | 매니저 사용자 FK | +| `notes` | text | 메모 | +| `bank_reference` | string(100) | 이체 참조번호 | +| `approved_by` | bigint | 승인자 FK | +| `approved_at` | timestamp | 승인 일시 | + +#### Relationships + +```php +$commission->management // BelongsTo SalesTenantManagement +$commission->partner // BelongsTo SalesPartner +$commission->manager // BelongsTo User +$commission->details // HasMany SalesCommissionDetail +$commission->approver // BelongsTo User +``` + +#### 주요 Scope + +```php +->pending() // 대기 상태 +->approved() // 승인 완료 +->paid() // 지급 완료 +->forPartner($partnerId) // 특정 파트너 +->forManager($managerUserId) // 특정 매니저 +->forScheduledMonth($year, $month) // 지급예정 월 +->paymentDateBetween($startDate, $endDate) // 입금일 기간 +``` + +#### 주요 Accessor + +```php +$commission->status_label // "대기", "승인", "지급완료", "취소" +$commission->payment_type_label // "계약금", "잔금" +$commission->total_commission // 파트너 + 매니저 수당 합계 +``` + +### SalesCommissionDetail + +**테이블**: `sales_commission_details` + +| 필드 | 타입 | 설명 | +|------|------|------| +| `commission_id` | bigint | 정산 FK (cascade delete) | +| `contract_product_id` | bigint | 계약 상품 FK | +| `registration_fee` | decimal(14,2) | 상품 가입비 | +| `base_amount` | decimal(14,2) | 수당 계산 기준액 | +| `partner_rate` / `manager_rate` | decimal(5,2) | 수당률 | +| `partner_commission` / `manager_commission` | decimal(14,2) | 수당액 | + +## 뷰 구성 + +### index.blade.php (메인 페이지) + +``` +┌─ 페이지 헤더 ────────────────────── +│ 제목: "영업수수료 정산" +│ [입금 등록] [CSV 다운로드] 버튼 +│ +├─ 필터 영역 ──────────────────────── +│ 년/월 선택 | 상태 | 입금구분 | 파트너 | 검색 +│ +├─ 통계 카드 (HTMX partial) ──────── +│ 대기 금액 | 승인 금액 | 지급 금액 | 월간 합계 +│ (파트너/매니저 수당 구분) +│ +├─ 정산 테이블 (HTMX partial) ────── +│ ☑ | 관리ID | 입금구분 | 금액 | 입금일 | 파트너 | 수당액 | 상태 | 작업 +│ └─ 체크박스: 일괄 승인/지급 선택 +│ └─ 인라인 작업: 승인, 지급완료, 취소 +│ └─ 페이지네이션 +│ +├─ 입금 등록 모달 (HTMX partial) ─── +│ 테넌트 선택 → 계약 상품 표시 +│ 입금구분(계약금/잔금) | 입금액 | 입금일 +│ +└─ 상세 모달 (HTMX partial) ──────── + 파트너/매니저 정보, 상품별 수당 내역 + 지급 추적 일정 +``` + +## HTMX 호환성 + +- Blade + HTMX 기반 (부분 업데이트 활용) +- 테이블, 통계 카드, 모달이 HTMX partial로 동작 +- 전체 페이지는 HX-Redirect 필요 (JavaScript 포함) diff --git a/features/settlement/subscriptions.md b/features/settlement/subscriptions.md new file mode 100644 index 0000000..5031f3a --- /dev/null +++ b/features/settlement/subscriptions.md @@ -0,0 +1,260 @@ +# 구독료정산 + +## 개요 + +구독료정산은 고객사별 구독 플랜과 과금을 관리하는 기능입니다. +플랜별 월정액 관리, 과금주기(월/연), 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 스크립트 포함