diff --git a/app/Http/Controllers/Sales/SalesDashboardController.php b/app/Http/Controllers/Sales/SalesDashboardController.php
index fe077e3d..0db5e311 100644
--- a/app/Http/Controllers/Sales/SalesDashboardController.php
+++ b/app/Http/Controllers/Sales/SalesDashboardController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Sales;
use App\Http\Controllers\Controller;
+use App\Models\Sales\SalesCommission;
use App\Models\Sales\SalesPartner;
use App\Models\Sales\SalesTenantManagement;
use App\Models\Sales\TenantProspect;
@@ -64,56 +65,107 @@ private function getDashboardData(Request $request): array
$endDate = now()->endOfMonth()->format('Y-m-d');
}
- // 통계 데이터 (임시 데이터 - 추후 실제 데이터로 교체)
+ $currentUserId = auth()->id();
+ $childrenIds = auth()->user()->children()->pluck('id')->toArray();
+ $partnerIds = array_merge([$currentUserId], $childrenIds);
+
+ // 현재 사용자의 영업파트너 정보 조회
+ $partner = SalesPartner::where('user_id', $currentUserId)->first();
+ $partnerId = $partner?->id;
+
+ // 나와 관련된 모든 수당 조회 (영업파트너로서 + 매니저로서)
+ $myCommissionsAsPartner = $partnerId
+ ? SalesCommission::forPartner($partnerId)->get()
+ : collect();
+ $myCommissionsAsManager = SalesCommission::forManager($currentUserId)->get();
+
+ // 판매자(영업파트너) 수당 계산
+ $partnerCommissionTotal = $myCommissionsAsPartner->sum('partner_commission');
+ $partnerCommissionPaid = $myCommissionsAsPartner->where('status', SalesCommission::STATUS_PAID)->sum('partner_commission');
+ $partnerCommissionPending = $myCommissionsAsPartner->where('status', SalesCommission::STATUS_PENDING)->sum('partner_commission');
+ $partnerCommissionApproved = $myCommissionsAsPartner->where('status', SalesCommission::STATUS_APPROVED)->sum('partner_commission');
+
+ // 매니저 수당 계산
+ $managerCommissionTotal = $myCommissionsAsManager->sum('manager_commission');
+ $managerCommissionPaid = $myCommissionsAsManager->where('status', SalesCommission::STATUS_PAID)->sum('manager_commission');
+ $managerCommissionPending = $myCommissionsAsManager->where('status', SalesCommission::STATUS_PENDING)->sum('manager_commission');
+ $managerCommissionApproved = $myCommissionsAsManager->where('status', SalesCommission::STATUS_APPROVED)->sum('manager_commission');
+
+ // 총 수당 계산 (중복 제거: 동일 commission에서 partner + manager인 경우)
+ $allCommissionIds = $myCommissionsAsPartner->pluck('id')->merge($myCommissionsAsManager->pluck('id'))->unique();
+ $totalContracts = $allCommissionIds->count();
+
+ // 통계 데이터 (실제 데이터)
+ $totalMembershipFee = $myCommissionsAsPartner->sum('payment_amount') + $myCommissionsAsManager->sum('payment_amount');
+ $totalCommission = $partnerCommissionTotal + $managerCommissionTotal;
+ $paidCommission = $partnerCommissionPaid + $managerCommissionPaid;
+ $commissionRate = $totalCommission > 0 ? round(($paidCommission / $totalCommission) * 100, 1) : 0;
+
$stats = [
- 'total_membership_fee' => 0, // 총 가입비
- 'total_commission' => 0, // 총 수당
- 'commission_rate' => 0, // 지급 승인 완료 비율
- 'total_contracts' => 0, // 전체 건수
- 'pending_membership_approval' => 0, // 가입 승인 대기
- 'pending_payment_approval' => 0, // 지급 승인 대기
+ 'total_membership_fee' => $totalMembershipFee, // 총 가입비
+ 'total_commission' => $totalCommission, // 총 수당
+ 'commission_rate' => $commissionRate, // 지급 완료 비율
+ 'total_contracts' => $totalContracts, // 전체 건수
+ 'pending_membership_approval' => $myCommissionsAsPartner->where('status', SalesCommission::STATUS_PENDING)->count()
+ + $myCommissionsAsManager->where('status', SalesCommission::STATUS_PENDING)->count(),
+ 'pending_payment_approval' => $myCommissionsAsPartner->where('status', SalesCommission::STATUS_APPROVED)->count()
+ + $myCommissionsAsManager->where('status', SalesCommission::STATUS_APPROVED)->count(),
];
- // 역할별 수당 상세
+ // 역할별 수당 상세 (실제 데이터)
$commissionByRole = [
[
'name' => '판매자',
'rate' => 20,
- 'amount' => 0,
+ 'amount' => $partnerCommissionTotal,
+ 'paid' => $partnerCommissionPaid,
+ 'pending' => $partnerCommissionPending,
+ 'approved' => $partnerCommissionApproved,
'color' => 'green',
],
[
'name' => '관리자',
'rate' => 5,
- 'amount' => 0,
+ 'amount' => $managerCommissionTotal,
+ 'paid' => $managerCommissionPaid,
+ 'pending' => $managerCommissionPending,
+ 'approved' => $managerCommissionApproved,
'color' => 'blue',
],
[
- 'name' => '매뉴제작 협업수당',
- 'rate' => null, // 별도
- 'amount' => null, // 운영팀 산정
- 'color' => 'red',
+ 'name' => '협업지원금',
+ 'rate' => null, // 메뉴당 2,000원
+ 'amount' => null, // 가입비 완납 시 계산
+ 'color' => 'purple',
],
];
- // 총 가입비 대비 수당
- $totalCommissionRatio = 0;
+ // 총 가입비 대비 수당 비율
+ $totalCommissionRatio = $totalMembershipFee > 0 ? round(($totalCommission / $totalMembershipFee) * 100, 1) : 0;
- // 수익 및 테넌트 관리 통계 (임시 데이터 - 추후 실제 데이터로 교체)
- $tenantStats = [
- 'total_tenants' => 0, // 관리 테넌트
- 'total_membership_revenue' => 0, // 총 가입비 실적
- 'total_commission_accumulated' => 0, // 누적 가입비 수당
- 'confirmed_commission' => 0, // 확정 가입비 수당
- ];
-
- // 테넌트 목록 (가망고객에서 전환된 테넌트만)
- // 전환된 가망고객의 tenant_id 목록 조회
- $convertedTenantIds = TenantProspect::whereNotNull('tenant_id')
+ // 1) 내가 등록한 가망고객에서 전환된 tenant_id (20% 수당)
+ $registeredTenantIds = TenantProspect::whereNotNull('tenant_id')
->where('status', TenantProspect::STATUS_CONVERTED)
+ ->whereIn('registered_by', $partnerIds)
->pluck('tenant_id')
->toArray();
+ // 2) 내가 매니저로 지정된 tenant_id (5% 수당)
+ $managedTenantIds = SalesTenantManagement::where('manager_user_id', $currentUserId)
+ ->pluck('tenant_id')
+ ->toArray();
+
+ // 두 목록 합치기 (중복 제거)
+ $convertedTenantIds = array_unique(array_merge($registeredTenantIds, $managedTenantIds));
+
+ // 수익 및 테넌트 관리 통계 (실제 데이터)
+ $tenantStats = [
+ 'total_tenants' => count($convertedTenantIds), // 관리 테넌트
+ 'total_membership_revenue' => $totalMembershipFee, // 총 가입비 실적
+ 'total_commission_accumulated' => $totalCommission, // 누적 수당
+ 'confirmed_commission' => $paidCommission, // 확정(지급완료) 수당
+ ];
+
// 전환된 테넌트만 조회 (최신순, 페이지네이션)
$tenants = Tenant::whereIn('id', $convertedTenantIds)
->orderBy('created_at', 'desc')
@@ -198,12 +250,26 @@ public function assignManager(int $tenantId, Request $request): JsonResponse
*/
public function refreshTenantList(Request $request): View
{
- // 전환된 가망고객의 tenant_id 목록 조회
- $convertedTenantIds = TenantProspect::whereNotNull('tenant_id')
+ // 테넌트 목록 (나와 연결된 계약만)
+ $currentUserId = auth()->id();
+ $childrenIds = auth()->user()->children()->pluck('id')->toArray();
+ $partnerIds = array_merge([$currentUserId], $childrenIds);
+
+ // 1) 내가 등록한 가망고객에서 전환된 tenant_id (20% 수당)
+ $registeredTenantIds = TenantProspect::whereNotNull('tenant_id')
->where('status', TenantProspect::STATUS_CONVERTED)
+ ->whereIn('registered_by', $partnerIds)
->pluck('tenant_id')
->toArray();
+ // 2) 내가 매니저로 지정된 tenant_id (5% 수당)
+ $managedTenantIds = SalesTenantManagement::where('manager_user_id', $currentUserId)
+ ->pluck('tenant_id')
+ ->toArray();
+
+ // 두 목록 합치기 (중복 제거)
+ $convertedTenantIds = array_unique(array_merge($registeredTenantIds, $managedTenantIds));
+
// 전환된 테넌트만 조회 (최신순, 페이지네이션)
$tenants = Tenant::whereIn('id', $convertedTenantIds)
->orderBy('created_at', 'desc')
diff --git a/app/Http/Controllers/TenantController.php b/app/Http/Controllers/TenantController.php
index 82c5ddc9..b437a240 100644
--- a/app/Http/Controllers/TenantController.php
+++ b/app/Http/Controllers/TenantController.php
@@ -4,6 +4,7 @@
use App\Services\TenantService;
use Illuminate\Http\Request;
+use Illuminate\Http\Response;
use Illuminate\View\View;
class TenantController extends Controller
@@ -15,8 +16,13 @@ public function __construct(
/**
* 테넌트 목록 (Blade 화면)
*/
- public function index(Request $request): View
+ public function index(Request $request): View|Response
{
+ // HTMX 요청 시 전체 페이지 리로드 (스크립트 실행 필요)
+ if ($request->header('HX-Request')) {
+ return response('', 200)->header('HX-Redirect', route('tenants.index'));
+ }
+
return view('tenants.index');
}
diff --git a/claudedocs/수당지급.md b/claudedocs/수당지급.md
new file mode 100644
index 00000000..92339fa5
--- /dev/null
+++ b/claudedocs/수당지급.md
@@ -0,0 +1,372 @@
+# 수당 지급 시스템
+
+> SAM 프로젝트 영업파트너 수당 지급 시스템 기술 문서
+>
+> 최종 수정: 2026-01-30
+
+---
+
+## 1. 개요
+
+### 1.1 목적
+이 문서는 SAM 영업관리 시스템의 **수당 계산 및 지급 프로세스**를 정의합니다.
+
+### 1.2 수당 유형
+
+| 수당 유형 | 수당률/금액 | 대상 | 기준 |
+|-----------|-------------|------|------|
+| **판매자 수당** | 20% | 가망고객 등록자 | 가입비의 50% 기준 |
+| **매니저 수당** | 5% | 지정된 매니저 | 가입비의 50% 기준 |
+| **협업지원금** | 메뉴당 2,000원 | 2단계 상위 파트너 | 가입비 완납 시 |
+
+---
+
+## 2. 수당 계산 로직
+
+### 2.1 기본 공식
+
+```
+기준 금액 = 총 가입비 ÷ 2 (50%)
+
+판매자 수당 = 기준 금액 × 20%
+매니저 수당 = 기준 금액 × 5%
+```
+
+### 2.2 계산 예시
+
+```
+총 가입비: 10,000,000원
+기준 금액: 5,000,000원 (50%)
+
+판매자 수당: 5,000,000 × 20% = 1,000,000원
+매니저 수당: 5,000,000 × 5% = 250,000원
+```
+
+### 2.3 입금 구분별 수당
+
+| 입금 구분 | 코드 | 설명 |
+|-----------|------|------|
+| **계약금** | `deposit` | 계약 시 선입금 |
+| **잔금** | `balance` | 계약 후 잔여금 |
+
+각 입금 시점마다 별도의 수당이 생성됩니다.
+
+---
+
+## 3. 협업지원금
+
+### 3.1 도입 배경
+
+**다단계 판매법 준수**: 다단계 판매법에서는 2단계 이상의 수당 지급이 금지되어 있습니다.
+이를 준수하면서도 상위 파트너의 기여를 인정하기 위해 "수당"이 아닌 "지원금" 형태로 지급합니다.
+
+### 3.2 지급 대상
+
+계약 체결자(판매자) 기준 **2단계 상위 파트너** (할아버지 파트너)
+
+```
+할아버지 파트너 ← 협업지원금 수령
+ │
+ ↓ (유치)
+아버지 파트너
+ │
+ ↓ (유치)
+손자 파트너 ← 테넌트 계약 체결 (판매자 수당 20%)
+ │
+ ↓
+테넌트 계약
+```
+
+### 3.3 산출 기준
+
+| 항목 | 내용 |
+|------|------|
+| **산출 공식** | 테넌트 메뉴 개수 × 2,000원 |
+| **지급 시점** | 가입비 완납 시 |
+| **지급 대상** | 계약자의 parent의 parent (2단계 상위) |
+
+### 3.4 계산 예시
+
+```
+[상황]
+- 손자 파트너가 테넌트 A와 계약 체결
+- 테넌트 A에 메뉴 50개 생성
+- 가입비 1,000만원 완납
+
+[수당/지원금 지급]
+손자 파트너 (판매자): 500만원 × 20% = 100만원
+매니저 (지정된 경우): 500만원 × 5% = 25만원
+할아버지 파트너: 50개 × 2,000원 = 10만원 (협업지원금)
+```
+
+### 3.5 지급 조건
+
+1. 계약자(손자)의 parent_id가 존재해야 함 (아버지 파트너)
+2. 아버지 파트너의 parent_id가 존재해야 함 (할아버지 파트너)
+3. 가입비가 **완납**되어야 함
+4. 테넌트에 메뉴가 생성되어 있어야 함
+
+> **주의**: 1단계 상위(아버지)는 협업지원금 대상이 아님.
+> 직접 유치한 파트너의 계약에 대해서는 별도 수당 정책 없음 (다단계법 준수).
+
+---
+
+## 4. 수당 지급 프로세스
+
+### 3.1 상태 흐름
+
+```
+┌─────────┐ ┌──────────┐ ┌─────────┐ ┌───────────┐
+│ 입금 │ ──▶ │ 대기 │ ──▶ │ 승인 │ ──▶ │ 지급완료 │
+│ 등록 │ │ pending │ │ approved│ │ paid │
+└─────────┘ └──────────┘ └─────────┘ └───────────┘
+ │
+ ▼
+ ┌──────────┐
+ │ 취소 │
+ │cancelled │
+ └──────────┘
+```
+
+### 3.2 상태별 설명
+
+| 상태 | 코드 | 설명 |
+|------|------|------|
+| **대기** | `pending` | 입금 등록 후 승인 대기 중 |
+| **승인** | `approved` | 본사 승인 완료, 지급 예정 |
+| **지급완료** | `paid` | 실제 지급 완료 |
+| **취소** | `cancelled` | 취소됨 (대기/승인 상태에서만 가능) |
+
+### 3.3 지급예정일 계산
+
+```php
+// 입금일 익월 10일
+$scheduledPaymentDate = $paymentDate->addMonth()->day(10);
+```
+
+**예시:**
+- 1월 15일 입금 → 2월 10일 지급예정
+- 1월 31일 입금 → 2월 10일 지급예정
+
+---
+
+## 4. 데이터베이스 구조
+
+### 4.1 sales_commissions 테이블
+
+```sql
+CREATE TABLE sales_commissions (
+ id BIGINT UNSIGNED PRIMARY KEY,
+ tenant_id BIGINT UNSIGNED NOT NULL,
+ management_id BIGINT UNSIGNED NOT NULL,
+
+ -- 입금 정보
+ payment_type ENUM('deposit', 'balance') NOT NULL,
+ payment_amount DECIMAL(15,2) NOT NULL,
+ payment_date DATE NOT NULL,
+
+ -- 수당 계산
+ base_amount DECIMAL(15,2) NOT NULL, -- 기준 금액 (가입비의 50%)
+ partner_rate DECIMAL(5,2) DEFAULT 20.00, -- 판매자 수당률
+ manager_rate DECIMAL(5,2) DEFAULT 5.00, -- 매니저 수당률
+ partner_commission DECIMAL(15,2) NOT NULL, -- 판매자 수당액
+ manager_commission DECIMAL(15,2) NOT NULL, -- 매니저 수당액
+
+ -- 지급 정보
+ scheduled_payment_date DATE NOT NULL, -- 지급예정일 (익월 10일)
+ actual_payment_date DATE NULL, -- 실제 지급일
+ status ENUM('pending', 'approved', 'paid', 'cancelled'),
+
+ -- 담당자
+ partner_id BIGINT UNSIGNED NOT NULL, -- 영업파트너 ID
+ manager_user_id BIGINT UNSIGNED NULL, -- 매니저 사용자 ID
+
+ -- 승인 정보
+ approved_by BIGINT UNSIGNED NULL,
+ approved_at TIMESTAMP NULL,
+
+ -- 기타
+ bank_reference VARCHAR(100) NULL, -- 이체 참조번호
+ notes TEXT NULL,
+
+ created_at TIMESTAMP,
+ updated_at TIMESTAMP,
+ deleted_at TIMESTAMP NULL
+);
+```
+
+### 4.2 sales_commission_details 테이블 (상품별 상세)
+
+```sql
+CREATE TABLE sales_commission_details (
+ id BIGINT UNSIGNED PRIMARY KEY,
+ commission_id BIGINT UNSIGNED NOT NULL,
+ contract_product_id BIGINT UNSIGNED NOT NULL,
+
+ registration_fee DECIMAL(15,2) NOT NULL, -- 상품 가입비
+ base_amount DECIMAL(15,2) NOT NULL, -- 기준 금액
+ partner_rate DECIMAL(5,2) NOT NULL, -- 상품별 판매자 수당률
+ manager_rate DECIMAL(5,2) NOT NULL, -- 상품별 매니저 수당률
+ partner_commission DECIMAL(15,2) NOT NULL, -- 판매자 수당액
+ manager_commission DECIMAL(15,2) NOT NULL, -- 매니저 수당액
+
+ created_at TIMESTAMP,
+ updated_at TIMESTAMP
+);
+```
+
+---
+
+## 5. 서비스 클래스
+
+### 5.1 SalesCommissionService
+
+경로: `app/Services/SalesCommissionService.php`
+
+#### 주요 메서드
+
+| 메서드 | 설명 |
+|--------|------|
+| `createCommission()` | 입금 등록 시 수당 생성 |
+| `approve()` | 수당 승인 처리 |
+| `markAsPaid()` | 지급완료 처리 |
+| `bulkApprove()` | 일괄 승인 |
+| `bulkMarkAsPaid()` | 일괄 지급완료 |
+| `cancel()` | 취소 처리 |
+| `getPartnerCommissionSummary()` | 영업파트너 수당 요약 |
+| `getManagerCommissionSummary()` | 매니저 수당 요약 |
+
+#### 수당 생성 예시
+
+```php
+$commission = $this->commissionService->createCommission(
+ managementId: $management->id,
+ paymentType: 'deposit', // 계약금
+ paymentAmount: 5000000, // 500만원
+ paymentDate: '2026-01-30'
+);
+```
+
+### 5.2 수당 요약 조회
+
+```php
+// 영업파트너 요약
+$summary = $this->commissionService->getPartnerCommissionSummary($partnerId);
+// [
+// 'scheduled_this_month' => 1000000, // 이번 달 지급예정
+// 'total_received' => 5000000, // 누적 수령
+// 'pending_amount' => 500000, // 대기중
+// 'contracts_this_month' => 3, // 이번 달 계약 건수
+// ]
+
+// 매니저 요약
+$summary = $this->commissionService->getManagerCommissionSummary($managerUserId);
+```
+
+---
+
+## 6. 대시보드 통계
+
+### 6.1 영업파트너 대시보드
+
+경로: `/sales/salesmanagement/dashboard`
+
+#### 표시 항목
+
+| 항목 | 설명 |
+|------|------|
+| 총 가입비 | 나와 관련된 계약의 총 입금액 |
+| 총 수당 | 판매자 수당 + 매니저 수당 합계 |
+| 지급 완료 비율 | (지급완료 수당 / 총 수당) × 100 |
+| 전체 건수 | 관련 계약 건수 |
+| 승인 대기 | pending 상태 건수 |
+| 지급 대기 | approved 상태 건수 |
+
+#### 역할별 수당 표시
+
+```
+┌─────────────────────────────────────────────┐
+│ 판매자 수당 (20%) │
+│ ├─ 총액: 1,000,000원 │
+│ ├─ 지급완료: 500,000원 │
+│ ├─ 승인완료: 300,000원 │
+│ └─ 대기중: 200,000원 │
+├─────────────────────────────────────────────┤
+│ 매니저 수당 (5%) │
+│ ├─ 총액: 250,000원 │
+│ ├─ 지급완료: 100,000원 │
+│ ├─ 승인완료: 100,000원 │
+│ └─ 대기중: 50,000원 │
+└─────────────────────────────────────────────┘
+```
+
+### 6.2 내 계약 현황 조회 범위
+
+대시보드에 표시되는 계약:
+1. **내가 등록한 가망고객** → 전환된 테넌트 (판매자 수당 20%)
+2. **내 하위 파트너가 등록한 가망고객** → 전환된 테넌트
+3. **내가 매니저로 지정된 계약** (매니저 수당 5%)
+
+```php
+// 1) 내가 등록한 가망고객에서 전환된 tenant_id
+$registeredTenantIds = TenantProspect::whereIn('registered_by', $partnerIds)
+ ->where('status', 'converted')
+ ->pluck('tenant_id');
+
+// 2) 내가 매니저로 지정된 tenant_id
+$managedTenantIds = SalesTenantManagement::where('manager_user_id', $currentUserId)
+ ->pluck('tenant_id');
+```
+
+---
+
+## 7. API 엔드포인트
+
+### 7.1 수당 정산 관리
+
+| Method | Endpoint | 설명 |
+|--------|----------|------|
+| GET | `/sales/commissions` | 정산 목록 조회 |
+| GET | `/sales/commissions/{id}` | 정산 상세 조회 |
+| POST | `/sales/commissions` | 입금 등록 (수당 생성) |
+| POST | `/sales/commissions/{id}/approve` | 승인 처리 |
+| POST | `/sales/commissions/{id}/paid` | 지급완료 처리 |
+| POST | `/sales/commissions/{id}/cancel` | 취소 처리 |
+| POST | `/sales/commissions/bulk-approve` | 일괄 승인 |
+| POST | `/sales/commissions/bulk-paid` | 일괄 지급완료 |
+
+---
+
+## 8. 관련 파일
+
+### 모델
+```
+app/Models/Sales/SalesCommission.php # 수당 정산 모델
+app/Models/Sales/SalesCommissionDetail.php # 수당 상세 내역
+app/Models/Sales/SalesPartner.php # 영업파트너 (누적 수당 저장)
+app/Models/Sales/SalesTenantManagement.php # 테넌트별 영업 관리
+```
+
+### 서비스
+```
+app/Services/SalesCommissionService.php # 수당 정산 서비스
+```
+
+### 컨트롤러
+```
+app/Http/Controllers/Sales/SalesCommissionController.php # 수당 정산 관리
+app/Http/Controllers/Sales/SalesDashboardController.php # 대시보드
+```
+
+---
+
+## 9. 변경 이력
+
+| 날짜 | 변경 내용 | 작성자 |
+|------|----------|--------|
+| 2026-01-30 | 최초 작성 | Claude |
+
+---
+
+> **참고:** 이 문서는 수당 관련 기능 개발 시 기준 문서로 사용됩니다.
+> 수당 정책 변경 시 반드시 이 문서를 먼저 업데이트하세요.
diff --git a/claudedocs/영업파트너구조.md b/claudedocs/영업파트너구조.md
index 4944ae71..954953a3 100644
--- a/claudedocs/영업파트너구조.md
+++ b/claudedocs/영업파트너구조.md
@@ -2,7 +2,7 @@ # 영업파트너 구조 설계서
> SAM 프로젝트 영업관리 시스템의 핵심 구조 문서
>
-> 최종 수정: 2026-01-27
+> 최종 수정: 2026-01-30
---
@@ -96,38 +96,47 @@ ### 3.2 역할 위임 시나리오
## 4. 수당/수익 구조
+> **상세 내용:** [수당지급.md](./수당지급.md) 참조
+
### 4.1 수당 유형
-| 수당 유형 | 기준 | 지급 대상 |
-|-----------|------|-----------|
-| **영업 수당** | 본인이 체결한 계약 | 계약 체결자 (sales 역할) |
-| **관리 수당** | 하위 파트너 실적 | manager 역할 보유자 |
-| **유치 수당** | 신규 파트너 유치 | recruiter 역할 보유자 |
+| 수당 유형 | 수당률/금액 | 지급 대상 | 설명 |
+|-----------|-------------|-----------|------|
+| **판매자 수당** | 20% | 가망고객 등록자 | 가입비의 50% × 20% |
+| **매니저 수당** | 5% | 지정된 매니저 | 가입비의 50% × 5% |
+| **협업지원금** | 메뉴당 2,000원 | 2단계 상위 파트너 | 가입비 완납 시 지급 |
### 4.2 수당 계산 원칙
```
-1. 영업 수당: 본인 계약 × 영업 수당률
-2. 관리 수당: 하위 N단계 실적 × 단계별 관리 수당률
-3. 유치 수당: 유치한 파트너 가입비 × 유치 수당률
+기준 금액 = 총 가입비의 50%
+
+1. 판매자 수당: 기준 금액 × 20% (가망고객 등록자)
+2. 매니저 수당: 기준 금액 × 5% (매니저로 지정된 파트너)
```
-### 4.3 계층별 수당 흐름 예시
+### 4.3 수당 흐름 예시
```
-고객 계약 100만원 발생 (계약자: 박지민)
+고객 계약 (가입비 1,000만원)
+ └─ 기준 금액: 500만원 (가입비의 50%)
-박지민 (레벨3, sales)
- → 영업 수당 20% = 20만원
+김철수 (가망고객 등록자, 판매자)
+ → 판매자 수당: 500만원 × 20% = 100만원
-이영희 (레벨2, 박지민의 상위)
- → 관리 수당 5% = 5만원 (sales 역할이지만 상위로서)
-
-김철수 (레벨1, 이영희의 상위)
- → 관리 수당 2% = 2만원 (레벨 차이에 따른 감소)
+이영희 (김철수가 지정한 매니저)
+ → 매니저 수당: 500만원 × 5% = 25만원
```
-> **주의:** 실제 수당률은 정책에 따라 변경될 수 있음
+### 4.4 수당 지급 프로세스
+
+```
+1. 입금 등록 → SalesCommission 생성 (status: pending)
+2. 본사 승인 → status: approved
+3. 지급 완료 → status: paid + 누적 수당 업데이트
+```
+
+> **참고:** 자세한 수당 시스템 구현 내용은 [수당지급.md](./수당지급.md) 참조
---
@@ -193,14 +202,18 @@ ### 6.1 완료된 기능
- [x] 역할 위임 기능 (상위 → 하위)
- [x] 역할 부여/제거 기능
- [x] 추천인(유치자) 관리
+- [x] **수당 자동 계산 (판매자 20%, 매니저 5%)**
+- [x] **수당 정산 시스템 (SalesCommission)**
+- [x] **수당 승인/지급 프로세스**
+- [x] **대시보드 통계 (실적, 수당 현황)**
+- [x] **가망고객 등록/관리**
+- [x] **테넌트 전환 프로세스**
### 6.2 구현 예정 기능
-- [ ] 계층별 수당 자동 계산
- [ ] 조직도 시각화 (트리 뷰)
-- [ ] 하위 파트너 실적 대시보드
- [ ] 유치 실적 관리
-- [ ] 수당 지급 내역 관리
+- [ ] 성과 분석 리포트
---
@@ -255,18 +268,46 @@ ## 8. 용어 정리
## 9. 관련 파일 경로
### MNG 프로젝트
+
+#### 모델
```
-app/Models/User.php # 사용자 모델 (영업파트너)
+app/Models/User.php # 사용자 모델 (영업파트너, parent_id)
+app/Models/Sales/SalesPartner.php # 영업파트너 정보
app/Models/Sales/SalesManagerDocument.php # 첨부 서류 모델
+app/Models/Sales/SalesCommission.php # 수당 정산 모델
+app/Models/Sales/SalesCommissionDetail.php # 수당 상세 내역
+app/Models/Sales/SalesTenantManagement.php # 테넌트별 영업 관리
+app/Models/Sales/TenantProspect.php # 가망고객 모델
+```
+
+#### 서비스
+```
+app/Services/SalesCommissionService.php # 수당 정산 서비스
app/Services/Sales/SalesManagerService.php # 영업파트너 서비스
-app/Http/Controllers/Sales/SalesManagerController.php
-resources/views/sales/managers/ # 뷰 파일들
+```
+
+#### 컨트롤러
+```
+app/Http/Controllers/Sales/SalesManagerController.php # 영업파트너 관리
+app/Http/Controllers/Sales/SalesDashboardController.php # 대시보드
+app/Http/Controllers/Sales/SalesProspectController.php # 가망고객 관리
+app/Http/Controllers/Sales/SalesCommissionController.php # 수당 정산
+```
+
+#### 뷰
+```
+resources/views/sales/managers/ # 영업파트너 관리
+resources/views/sales/dashboard/ # 대시보드
+resources/views/sales/prospects/ # 가망고객 관리
+resources/views/sales/commissions/ # 수당 정산
```
### API 프로젝트
```
database/migrations/2026_01_27_200000_add_sales_manager_fields_to_users_table.php
database/migrations/2026_01_27_200100_create_sales_manager_documents_table.php
+database/migrations/..._create_sales_commissions_table.php
+database/migrations/..._create_tenant_prospects_table.php
```
---
@@ -277,6 +318,9 @@ ## 10. 변경 이력
|------|----------|--------|
| 2026-01-27 | 최초 작성 | Claude |
| 2026-01-27 | 역할 위임/부여/제거 기능 구현 완료 | Claude |
+| 2026-01-30 | 수당 구조 업데이트 (판매자 20%, 매니저 5%) | Claude |
+| 2026-01-30 | 수당 시스템 구현 완료 반영 | Claude |
+| 2026-01-30 | 관련 파일 경로 업데이트 | Claude |
---
diff --git a/resources/views/finance/corporate-cards.blade.php b/resources/views/finance/corporate-cards.blade.php
index 5fba401e..d62ba9b9 100644
--- a/resources/views/finance/corporate-cards.blade.php
+++ b/resources/views/finance/corporate-cards.blade.php
@@ -50,61 +50,8 @@
const XCircle = createIcon('x-circle');
function CorporateCardsManagement() {
- // 카드 목록 데이터
- const [cards, setCards] = useState([
- {
- id: 1,
- cardName: '업무용 법인카드',
- cardCompany: '삼성카드',
- cardNumber: '9410-1234-5678-9012',
- cardType: 'credit',
- paymentDay: 15,
- creditLimit: 10000000,
- currentUsage: 3500000,
- holder: '김대표',
- status: 'active',
- memo: '일반 업무용'
- },
- {
- id: 2,
- cardName: '마케팅 법인카드',
- cardCompany: '현대카드',
- cardNumber: '5412-9876-5432-1098',
- cardType: 'credit',
- paymentDay: 25,
- creditLimit: 5000000,
- currentUsage: 2100000,
- holder: '박마케팅',
- status: 'active',
- memo: '마케팅/광고비 전용'
- },
- {
- id: 3,
- cardName: '개발팀 체크카드',
- cardCompany: '국민카드',
- cardNumber: '4532-1111-2222-3333',
- cardType: 'debit',
- paymentDay: 0,
- creditLimit: 0,
- currentUsage: 850000,
- holder: '이개발',
- status: 'active',
- memo: '개발 장비/소프트웨어 구매'
- },
- {
- id: 4,
- cardName: '예비 법인카드',
- cardCompany: '신한카드',
- cardNumber: '9876-5555-4444-3333',
- cardType: 'credit',
- paymentDay: 10,
- creditLimit: 3000000,
- currentUsage: 0,
- holder: '최관리',
- status: 'inactive',
- memo: '비상용'
- }
- ]);
+ // 카드 목록 데이터 (빈 배열로 시작 - 실제 데이터는 서버 연동 후 로드)
+ const [cards, setCards] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [filterStatus, setFilterStatus] = useState('all');
@@ -120,7 +67,10 @@ function CorporateCardsManagement() {
cardType: 'credit',
paymentDay: 15,
creditLimit: '',
- holder: '',
+ cardHolderName: '',
+ actualUser: '',
+ expiryDate: '',
+ cvc: '',
status: 'active',
memo: ''
};
@@ -158,7 +108,8 @@ function CorporateCardsManagement() {
const filteredCards = cards.filter(card => {
const matchesSearch = card.cardName.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.cardCompany.toLowerCase().includes(searchTerm.toLowerCase()) ||
- card.holder.toLowerCase().includes(searchTerm.toLowerCase());
+ card.actualUser.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (card.cardHolderName && card.cardHolderName.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesStatus = filterStatus === 'all' || card.status === filterStatus;
return matchesSearch && matchesStatus;
});
@@ -181,7 +132,10 @@ function CorporateCardsManagement() {
cardType: card.cardType,
paymentDay: card.paymentDay,
creditLimit: card.creditLimit,
- holder: card.holder,
+ cardHolderName: card.cardHolderName || '',
+ actualUser: card.actualUser || '',
+ expiryDate: card.expiryDate || '',
+ cvc: card.cvc || '',
status: card.status,
memo: card.memo
});
@@ -190,7 +144,7 @@ function CorporateCardsManagement() {
// 카드 저장
const handleSaveCard = () => {
- if (!formData.cardName || !formData.cardNumber || !formData.holder) {
+ if (!formData.cardName || !formData.cardNumber || !formData.cardHolderName || !formData.actualUser) {
alert('필수 항목을 입력해주세요.');
return;
}
@@ -215,9 +169,22 @@ function CorporateCardsManagement() {
setEditingCard(null);
};
- // 카드 삭제
- const handleDeleteCard = (id) => {
- if (confirm('정말 삭제하시겠습니까?')) {
+ // 카드 비활성화 (소프트 삭제)
+ const handleDeactivateCard = (id) => {
+ if (confirm('카드를 비활성화하시겠습니까?\n(목록에서 숨겨지지만 데이터는 유지됩니다)')) {
+ setCards(prev => prev.map(card =>
+ card.id === id ? { ...card, status: 'inactive' } : card
+ ));
+ if (showModal) {
+ setShowModal(false);
+ setEditingCard(null);
+ }
+ }
+ };
+
+ // 카드 영구삭제
+ const handlePermanentDeleteCard = (id) => {
+ if (confirm('⚠️ 카드를 영구 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다.')) {
setCards(prev => prev.filter(card => card.id !== id));
if (showModal) {
setShowModal(false);
@@ -312,7 +279,7 @@ className="flex items-center gap-2 px-4 py-2 bg-violet-600 hover:bg-violet-700 t
사용자
-{card.holder}
+이용자명
+{card.cardHolderName || '-'}
+실사용자
+{card.actualUser}
+유효기간(년도/월)
+{card.expiryDate || '-'}
카드종류
@@ -487,28 +462,60 @@ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus