docs:정산관리 개발문서 추가 (4개 메뉴)
- 영업수수료정산: 입금등록, 수당자동계산, 승인프로세스, 지급추적 - 컨설팅비용정산: 상담시간×시급 정산, 상태관리 - 고객사정산: 월별 매출/수수료/비용 순정산액 관리 - 구독료정산: 플랜별 과금, MRR/ARR 집계, 구독 라이프사이클 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
features/settlement/README.md
Normal file
102
features/settlement/README.md
Normal file
@@ -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` | 고객사 마스터 (참조) |
|
||||
148
features/settlement/consulting-fees.md
Normal file
148
features/settlement/consulting-fees.md
Normal file
@@ -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 스크립트 포함
|
||||
165
features/settlement/customer-settlements.md
Normal file
165
features/settlement/customer-settlements.md
Normal file
@@ -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 스크립트 포함
|
||||
264
features/settlement/sales-commissions.md
Normal file
264
features/settlement/sales-commissions.md
Normal file
@@ -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 포함)
|
||||
260
features/settlement/subscriptions.md
Normal file
260
features/settlement/subscriptions.md
Normal file
@@ -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 스크립트 포함
|
||||
Reference in New Issue
Block a user