diff --git a/app/Http/Controllers/Sales/AdminProspectController.php b/app/Http/Controllers/Sales/AdminProspectController.php new file mode 100644 index 00000000..063cb895 --- /dev/null +++ b/app/Http/Controllers/Sales/AdminProspectController.php @@ -0,0 +1,133 @@ +middleware(function ($request, $next) { + if (!auth()->user()->isAdmin() && !auth()->user()->isSuperAdmin()) { + abort(403, '관리자만 접근할 수 있습니다.'); + } + return $next($request); + }); + } + + /** + * 전체 고객 목록 페이지 + */ + public function index(Request $request): View|Response + { + if ($request->header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('sales.admin-prospects.index')); + } + + // 영업 역할을 가진 사용자 목록 (영업파트너) + $salesPartners = User::whereHas('userRoles', function ($q) { + $q->whereHas('role', function ($rq) { + $rq->whereIn('name', ['sales', 'manager', 'recruiter']); + }); + })->orderBy('name')->get(); + + // 필터 + $filters = [ + 'search' => $request->get('search'), + 'status' => $request->get('status'), + 'registered_by' => $request->get('registered_by'), // 특정 영업파트너 필터 + ]; + + // 쿼리 빌드 + $query = TenantProspect::with(['registeredBy', 'tenant']); + + // 검색 + if (!empty($filters['search'])) { + $search = $filters['search']; + $query->where(function ($q) use ($search) { + $q->where('company_name', 'like', "%{$search}%") + ->orWhere('business_number', 'like', "%{$search}%") + ->orWhere('ceo_name', 'like', "%{$search}%") + ->orWhere('contact_phone', 'like', "%{$search}%"); + }); + } + + // 상태 필터 + if (!empty($filters['status'])) { + $query->where('status', $filters['status']); + } + + // 영업파트너 필터 + if (!empty($filters['registered_by'])) { + $query->where('registered_by', $filters['registered_by']); + } + + $prospects = $query->orderByDesc('created_at')->paginate(20); + + // 각 가망고객의 진행률 계산 + foreach ($prospects as $prospect) { + $progress = SalesScenarioChecklist::getProspectProgress($prospect->id); + $prospect->sales_progress = $progress['sales']['percentage']; + $prospect->manager_progress = $progress['manager']['percentage']; + + // management 정보 + $management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first(); + $prospect->hq_status = $management?->hq_status ?? 'pending'; + $prospect->hq_status_label = $management?->hq_status_label ?? '대기'; + $prospect->manager_user = $management?->manager; + } + + // 전체 통계 + $stats = [ + 'total' => TenantProspect::count(), + 'active' => TenantProspect::where('status', TenantProspect::STATUS_ACTIVE)->count(), + 'expired' => TenantProspect::where('status', TenantProspect::STATUS_EXPIRED)->count(), + 'converted' => TenantProspect::where('status', TenantProspect::STATUS_CONVERTED)->count(), + ]; + + // 영업파트너별 통계 + $partnerStats = TenantProspect::selectRaw('registered_by, COUNT(*) as total') + ->groupBy('registered_by') + ->with('registeredBy') + ->get() + ->map(function ($item) { + return [ + 'user' => $item->registeredBy, + 'total' => $item->total, + ]; + }); + + return view('sales.admin-prospects.index', compact('prospects', 'stats', 'salesPartners', 'partnerStats', 'filters')); + } + + /** + * 고객 상세 모달 + */ + public function modalShow(int $id): View + { + $prospect = TenantProspect::with(['registeredBy', 'tenant'])->findOrFail($id); + + // 진행률 + $progress = SalesScenarioChecklist::getProspectProgress($prospect->id); + $prospect->sales_progress = $progress['sales']['percentage']; + $prospect->manager_progress = $progress['manager']['percentage']; + + // management 정보 + $management = SalesTenantManagement::findOrCreateByProspect($prospect->id); + + return view('sales.admin-prospects.partials.show-modal', compact('prospect', 'management', 'progress')); + } +} diff --git a/database/seeders/AdminProspectMenuSeeder.php b/database/seeders/AdminProspectMenuSeeder.php new file mode 100644 index 00000000..5f7194cc --- /dev/null +++ b/database/seeders/AdminProspectMenuSeeder.php @@ -0,0 +1,73 @@ +where('name', '영업관리') + ->whereNull('parent_id') + ->value('id'); + + if (!$salesParentId) { + $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; + } + + // 고객 관리 메뉴 찾기 (sort_order 확인) + $prospectMenu = Menu::where('tenant_id', $tenantId) + ->where('parent_id', $salesParentId) + ->where('name', '고객 관리') + ->first(); + + $sortOrder = ($prospectMenu?->sort_order ?? 2) + 1; + + // 영업파트너 고객관리 메뉴 존재 여부 확인 + $existingMenu = Menu::where('tenant_id', $tenantId) + ->where('parent_id', $salesParentId) + ->where('name', '영업파트너 고객관리') + ->first(); + + if ($existingMenu) { + $this->command->info('영업파트너 고객관리 메뉴가 이미 존재합니다.'); + return; + } + + // 메뉴 생성 + Menu::create([ + 'tenant_id' => $tenantId, + 'parent_id' => $salesParentId, + 'name' => '영업파트너 고객관리', + 'url' => '/sales/admin-prospects', + 'icon' => 'users', + 'sort_order' => $sortOrder, + 'is_active' => true, + 'required_roles' => json_encode(['admin', 'super_admin']), // 관리자만 접근 가능 + ]); + + $this->command->info('영업파트너 고객관리 메뉴 생성 완료 (sort_order: ' . $sortOrder . ')'); + + // 결과 출력 + $this->command->info(''); + $this->command->info('=== 영업관리 하위 메뉴 ==='); + $children = Menu::where('parent_id', $salesParentId) + ->orderBy('sort_order') + ->get(['name', 'url', 'sort_order']); + + foreach ($children as $child) { + $this->command->info("{$child->sort_order}. {$child->name} ({$child->url})"); + } + } +} diff --git a/resources/views/sales/admin-prospects/index.blade.php b/resources/views/sales/admin-prospects/index.blade.php new file mode 100644 index 00000000..b22e1d1f --- /dev/null +++ b/resources/views/sales/admin-prospects/index.blade.php @@ -0,0 +1,259 @@ +@extends('layouts.app') + +@section('title', '영업파트너 고객관리') + +@section('content') +
전체 영업파트너의 고객 현황을 관리합니다 (관리자 전용)
+| 업체명 | +담당 파트너 | +담당 매니저 | +영업 진행률 | +매니저 진행률 | +개발 상태 | +상태 | +등록일 | +관리 | +
|---|---|---|---|---|---|---|---|---|
|
+ {{ $prospect->company_name }}
+ {{ $prospect->business_number }}
+ |
+ + @if($prospect->registeredBy) + {{ $prospect->registeredBy->name }} + @else + - + @endif + | ++ @if($prospect->manager_user) + {{ $prospect->manager_user->name }} + @else + 미지정 + @endif + | +
+
+
+
+
+
+ {{ $prospect->sales_progress }}%
+ |
+
+
+
+
+
+
+ {{ $prospect->manager_progress }}%
+ |
+ + + {{ $prospect->hq_status_label }} + + | ++ + {{ $prospect->status_label }} + + | ++ {{ $prospect->created_at->format('Y-m-d') }} + | ++ + | +
| + 등록된 고객이 없습니다. + | +||||||||
로딩 중...
+{{ $prospect->business_number }}
+대표자
+{{ $prospect->ceo_name ?? '-' }}
+연락처
+{{ $prospect->contact_phone ?? '-' }}
+이메일
+{{ $prospect->contact_email ?? '-' }}
+상태
+ + {{ $prospect->status_label }} + +담당 파트너
+{{ $prospect->registeredBy?->name ?? '-' }}
+담당 매니저
+{{ $management->manager?->name ?? '미지정' }}
+등록일: {{ $prospect->created_at->format('Y-m-d H:i') }}
+ @if($prospect->expires_at) +영업권 만료: {{ $prospect->expires_at->format('Y-m-d') }}
+ @endif +