getDashboardData($request); // 영업파트너 수당 정보 추가 $data = array_merge($data, $this->getCommissionData()); return view('sales.dashboard.index', $data); } /** * HTMX 부분 새로고침용 데이터 반환 */ public function refresh(Request $request): View { $data = $this->getDashboardData($request); return view('sales.dashboard.partials.data-container', $data); } /** * 대시보드 데이터 조회 */ private function getDashboardData(Request $request): array { // 기간 설정 $period = $request->input('period', 'month'); // month or custom $year = $request->input('year', now()->year); $month = $request->input('month', now()->month); // 기간 설정 모드일 경우 if ($period === 'custom') { $startDate = $request->input('start_date', now()->startOfMonth()->format('Y-m-d')); $endDate = $request->input('end_date', now()->format('Y-m-d')); } else { $startDate = now()->startOfMonth()->format('Y-m-d'); $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' => $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' => $partnerCommissionTotal, 'paid' => $partnerCommissionPaid, 'pending' => $partnerCommissionPending, 'approved' => $partnerCommissionApproved, 'color' => 'green', ], [ 'name' => '관리자', 'rate' => null, // 1개월 구독료 (퍼센트가 아닌 고정 금액) 'rate_label' => '1개월 구독료', 'amount' => $managerCommissionTotal, 'paid' => $managerCommissionPaid, 'pending' => $managerCommissionPending, 'approved' => $managerCommissionApproved, 'color' => 'blue', ], [ 'name' => '협업지원금', 'rate' => null, // 메뉴당 2,000원 'amount' => null, // 가입비 완납 시 계산 'color' => 'purple', ], ]; // === 인계(handover) 완료된 가망고객의 수당 계산 === // 내가 등록한 가망고객 중 인계 완료된 것들의 계약 금액 조회 $handoverProspectIds = TenantProspect::whereIn('registered_by', $partnerIds) ->pluck('id') ->toArray(); // 인계 완료된 가망고객의 management_id 조회 $handoverManagements = SalesTenantManagement::whereIn('tenant_prospect_id', $handoverProspectIds) ->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER) ->get(); // 인계 완료된 계약의 가입비 합계 $handoverManagementIds = $handoverManagements->pluck('id')->toArray(); $handoverTotalRegFee = SalesContractProduct::whereIn('management_id', $handoverManagementIds) ->sum('registration_fee'); // 수당 계산: 가입비 × 50% × 20% = 가입비 × 10% $handoverPartnerCommission = (int)($handoverTotalRegFee * 0.10); // 내가 매니저로 지정된 인계 완료 건의 수당 계산 $managedHandoverManagements = SalesTenantManagement::where('manager_user_id', $currentUserId) ->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER) ->get(); $managedHandoverManagementIds = $managedHandoverManagements->pluck('id')->toArray(); // 매니저 수당: 1개월 구독료 (퍼센트가 아닌 고정 금액) $handoverManagerCommission = (int)SalesContractProduct::whereIn('management_id', $managedHandoverManagementIds) ->sum('subscription_fee'); // 기존 수당에 인계 완료 수당 추가 $partnerCommissionTotal += $handoverPartnerCommission; $managerCommissionTotal += $handoverManagerCommission; $totalMembershipFee += $handoverTotalRegFee; $totalCommission = $partnerCommissionTotal + $managerCommissionTotal; // 역할별 수당 업데이트 $commissionByRole[0]['amount'] = $partnerCommissionTotal; $commissionByRole[1]['amount'] = $managerCommissionTotal; // 총 가입비 대비 수당 비율 $totalCommissionRatio = $totalMembershipFee > 0 ? round(($totalCommission / $totalMembershipFee) * 100, 1) : 0; // 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) ->whereNotNull('tenant_id') ->pluck('tenant_id') ->toArray(); // 두 목록 합치기 (중복 제거) $convertedTenantIds = array_unique(array_merge($registeredTenantIds, $managedTenantIds)); // 3) 인계 완료된 가망고객 ID 조회 $handoverCompletedProspectIds = SalesTenantManagement::whereNotNull('tenant_prospect_id') ->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER) ->pluck('tenant_prospect_id') ->toArray(); // 4) 내가 직접 등록한 가망고객 (진행중 - 인계 완료되지 않은 것) // 하위 파트너가 등록한 것은 "유치 파트너 현황" 탭에서 표시 $prospects = TenantProspect::where('registered_by', $currentUserId) ->whereIn('status', [TenantProspect::STATUS_ACTIVE, TenantProspect::STATUS_EXPIRED]) ->whereNotIn('id', $handoverCompletedProspectIds) ->orderBy('created_at', 'desc') ->get(); // 5) 내가 직접 등록하고 인계 완료된 가망고객 (히스토리) $handoverProspects = TenantProspect::where('registered_by', $currentUserId) ->whereIn('id', $handoverCompletedProspectIds) ->orderBy('created_at', 'desc') ->get(); // 인계 완료된 가망고객 수 $handoverProspectCount = count($handoverManagementIds); // 수익 및 테넌트 관리 통계 (실제 데이터) $tenantStats = [ 'total_tenants' => count($convertedTenantIds) + $handoverProspectCount, // 관리 테넌트 + 인계완료 'total_prospects' => $prospects->count(), // 진행중 가망고객 'total_membership_revenue' => $totalMembershipFee, // 총 가입비 실적 'total_commission_accumulated' => $totalCommission, // 누적 수당 'confirmed_commission' => $paidCommission, // 확정(지급완료) 수당 ]; // 통계 업데이트 $stats['total_membership_fee'] = $totalMembershipFee; $stats['total_commission'] = $totalCommission; // 전환된 테넌트만 조회 (최신순, 페이지네이션) $tenants = Tenant::whereIn('id', $convertedTenantIds) ->orderBy('created_at', 'desc') ->paginate(10) ->withQueryString(); // 각 테넌트의 영업 관리 정보 로드 $tenantIds = $tenants->pluck('id')->toArray(); $managements = SalesTenantManagement::whereIn('tenant_id', $tenantIds) ->with('manager') ->get() ->keyBy('tenant_id'); // 내가 유치한 영업파트너 목록 (드롭다운용) $allManagers = auth()->user()->children() ->where('is_active', true) ->get(['id', 'name', 'email']); return compact( 'stats', 'commissionByRole', 'totalCommissionRatio', 'tenantStats', 'tenants', 'prospects', 'handoverProspects', 'managements', 'allManagers', 'period', 'year', 'month', 'startDate', 'endDate' ); } /** * 매니저 지정 변경 */ public function assignManager(int $tenantId, Request $request): JsonResponse { $request->validate([ 'manager_id' => 'required|integer', ]); $tenant = Tenant::findOrFail($tenantId); $managerId = $request->input('manager_id'); // 테넌트 영업 관리 정보 조회 또는 생성 $management = SalesTenantManagement::findOrCreateByTenant($tenantId); if ($managerId === 0) { // 본인으로 설정 (현재 로그인 사용자) $manager = auth()->user(); $management->update([ 'manager_user_id' => $manager->id, ]); } else { // 특정 매니저 지정 $manager = User::find($managerId); if (!$manager) { return response()->json([ 'success' => false, 'message' => '매니저를 찾을 수 없습니다.', ], 404); } $management->update([ 'manager_user_id' => $manager->id, ]); } return response()->json([ 'success' => true, 'manager' => [ 'id' => $manager->id, 'name' => $manager->name, ], ]); } /** * 가망고객에 매니저 지정 */ public function assignProspectManager(int $prospectId, Request $request): JsonResponse { $request->validate([ 'manager_id' => 'required|integer', ]); $prospect = TenantProspect::findOrFail($prospectId); $managerId = $request->input('manager_id'); // 가망고객 영업 관리 정보 조회 또는 생성 $management = SalesTenantManagement::findOrCreateByProspect($prospectId); if ($managerId === 0) { // 본인으로 설정 (현재 로그인 사용자) $manager = auth()->user(); $management->update([ 'manager_user_id' => $manager->id, ]); } else { // 특정 매니저 지정 $manager = User::find($managerId); if (!$manager) { return response()->json([ 'success' => false, 'message' => '매니저를 찾을 수 없습니다.', ], 404); } $management->update([ 'manager_user_id' => $manager->id, ]); } return response()->json([ 'success' => true, 'manager' => [ 'id' => $manager->id, 'name' => $manager->name, ], ]); } /** * 테넌트 리스트 부분 새로고침 (HTMX) */ public function refreshTenantList(Request $request): View { // 테넌트 목록 (나와 연결된 계약만) $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)); // 3) 내가 직접 등록한 가망고객 (아직 전환되지 않은 것 - active 상태) $prospects = TenantProspect::where('registered_by', $currentUserId) ->whereIn('status', [TenantProspect::STATUS_ACTIVE, TenantProspect::STATUS_EXPIRED]) ->orderBy('created_at', 'desc') ->get(); // 전환된 테넌트만 조회 (최신순, 페이지네이션) $tenants = Tenant::whereIn('id', $convertedTenantIds) ->orderBy('created_at', 'desc') ->paginate(10) ->withQueryString(); // 각 테넌트의 영업 관리 정보 로드 $tenantIds = $tenants->pluck('id')->toArray(); $managements = SalesTenantManagement::whereIn('tenant_id', $tenantIds) ->with('manager') ->get() ->keyBy('tenant_id'); // 내가 유치한 영업파트너 목록 (드롭다운용) $allManagers = auth()->user()->children() ->where('is_active', true) ->get(['id', 'name', 'email']); return view('sales.dashboard.partials.tenant-list', compact( 'tenants', 'prospects', 'managements', 'allManagers' )); } /** * 매니저 목록 조회 (드롭다운용) */ public function getManagers(Request $request): JsonResponse { // HQ 테넌트의 사용자 중 매니저 역할이 있는 사용자 조회 $managers = User::whereHas('tenants', function ($query) { $query->where('tenant_type', 'HQ'); })->get(['id', 'name', 'email']); return response()->json([ 'success' => true, 'managers' => $managers, ]); } /** * 유치 파트너 활동 현황 (HTMX 탭 로드) */ public function partnerActivity(Request $request): View { $data = $this->getPartnerActivityData(); return view('sales.dashboard.partials.partner-activity', $data); } /** * 유치 파트너 활동 데이터 조회 */ private function getPartnerActivityData(): array { $currentUser = auth()->user(); $currentUserId = $currentUser->id; // 직접 유치한 하위 파트너 목록 (parent_id가 현재 사용자인 사용자들) $recruitedPartners = User::where('parent_id', $currentUserId) ->where('is_active', true) ->with(['userRoles.role']) ->get(); $partnerIds = $recruitedPartners->pluck('id')->toArray(); // 요약 통계 계산 $summaryStats = $this->calculatePartnerSummaryStats($partnerIds, $currentUserId); // 파트너별 상세 활동 데이터 $partnerActivities = $this->getPartnerActivitiesDetail($recruitedPartners, $currentUserId); return [ 'summaryStats' => $summaryStats, 'partnerActivities' => $partnerActivities, 'recruitedPartners' => $recruitedPartners, ]; } /** * 유치 파트너 요약 통계 계산 */ private function calculatePartnerSummaryStats(array $partnerIds, int $currentUserId): array { // 유치 파트너 수 $partnerCount = count($partnerIds); // 하위 파트너들이 등록한 총 영업권(명함) 수 $totalProspects = TenantProspect::whereIn('registered_by', $partnerIds)->count(); // 예상 수당 계산을 위해 먼저 가입비/구독료 정보 조회 $prospectIds = TenantProspect::whereIn('registered_by', $partnerIds)->pluck('id')->toArray(); $managementIds = SalesTenantManagement::whereIn('tenant_prospect_id', $prospectIds)->pluck('id')->toArray(); // 하위 파트너들의 계약 건수 (가입비가 설정된 건수) $contractedManagementCount = SalesContractProduct::whereIn('management_id', $managementIds) ->where('registration_fee', '>', 0) ->distinct('management_id') ->count('management_id'); $totalConversions = $contractedManagementCount; // 매니저 예상 수당: 1개월 구독료 (퍼센트가 아닌 고정 금액) $totalSubscriptionFee = SalesContractProduct::whereIn('management_id', $managementIds)->sum('subscription_fee'); // 확정 수당 (SalesCommission에서) $confirmedCommission = SalesCommission::where('manager_user_id', $currentUserId) ->whereHas('partner', function ($query) use ($partnerIds) { $query->whereIn('user_id', $partnerIds); }) ->sum('manager_commission'); $expectedFromFee = (int)$totalSubscriptionFee; // 1개월 구독료 // 최종 예상 수당 (확정 + 예상 중 큰 값) $expectedCommission = max($confirmedCommission, $expectedFromFee); return [ 'partner_count' => $partnerCount, 'total_prospects' => $totalProspects, 'total_conversions' => $totalConversions, 'expected_commission' => $expectedCommission, ]; } /** * 파트너별 상세 활동 데이터 */ private function getPartnerActivitiesDetail($recruitedPartners, int $currentUserId): array { $activities = []; foreach ($recruitedPartners as $partner) { // 파트너의 영업파트너 정보 $salesPartner = SalesPartner::where('user_id', $partner->id)->first(); // 파트너가 등록한 영업권 수 $prospectCount = TenantProspect::where('registered_by', $partner->id)->count(); // 진행 중인 영업권 (active 상태) $activeProspects = TenantProspect::where('registered_by', $partner->id) ->where('status', TenantProspect::STATUS_ACTIVE) ->count(); // 계약 성사 건수 $conversions = TenantProspect::where('registered_by', $partner->id) ->where('status', TenantProspect::STATUS_CONVERTED) ->count(); // 이 파트너로 인한 나의 매니저 수당 (확정 수당) $confirmedCommission = 0; if ($salesPartner) { $confirmedCommission = SalesCommission::where('manager_user_id', $currentUserId) ->where('partner_id', $salesPartner->id) ->sum('manager_commission'); } // 예상 수당 계산: 파트너가 등록한 가망고객의 1개월 구독료 $prospectIds = TenantProspect::where('registered_by', $partner->id)->pluck('id')->toArray(); $managementIds = SalesTenantManagement::whereIn('tenant_prospect_id', $prospectIds)->pluck('id')->toArray(); $totalRegistrationFee = SalesContractProduct::whereIn('management_id', $managementIds)->sum('registration_fee'); $totalSubscriptionFee = SalesContractProduct::whereIn('management_id', $managementIds)->sum('subscription_fee'); $expectedCommission = (int)$totalSubscriptionFee; // 1개월 구독료 // 최종 매니저 수당 (확정 + 예상 중 큰 값, 또는 합산) $managerCommission = max($confirmedCommission, $expectedCommission); $hasRegistrationFee = $totalRegistrationFee > 0; // 최근 활동 내역 (최근 전환된 테넌트 5개) $recentTenants = TenantProspect::where('registered_by', $partner->id) ->where('status', TenantProspect::STATUS_CONVERTED) ->with(['tenant']) ->orderBy('converted_at', 'desc') ->limit(5) ->get(); // 파트너의 모든 가망고객 (진행률 조회용) $allProspects = TenantProspect::where('registered_by', $partner->id) ->whereIn('status', [TenantProspect::STATUS_ACTIVE, TenantProspect::STATUS_EXPIRED]) ->orderBy('created_at', 'desc') ->get(); // 활동 상태 판단 $lastActivity = TenantProspect::where('registered_by', $partner->id) ->orderBy('updated_at', 'desc') ->first(); $status = 'inactive'; if ($lastActivity) { $daysSinceActivity = now()->diffInDays($lastActivity->updated_at); if ($daysSinceActivity <= 7) { $status = 'active'; } elseif ($daysSinceActivity <= 30) { $status = 'moderate'; } } // 역할 정보 $roles = $partner->userRoles->pluck('role.name')->filter()->toArray(); $roleLabel = !empty($roles) ? implode(', ', $roles) : '영업'; $activities[] = [ 'partner' => $partner, 'role_label' => $roleLabel, 'prospect_count' => $prospectCount, 'active_prospects' => $activeProspects, 'conversions' => $conversions, 'manager_commission' => $managerCommission, 'has_registration_fee' => $hasRegistrationFee, 'status' => $status, 'recent_tenants' => $recentTenants, 'all_prospects' => $allProspects, ]; } return $activities; } /** * 영업파트너 수당 정보 조회 */ private function getCommissionData(): array { $user = auth()->user(); $commissionSummary = []; $recentCommissions = collect(); // 현재 사용자가 영업파트너인지 확인 $partner = SalesPartner::where('user_id', $user->id) ->where('status', 'active') ->first(); if ($partner) { $commissionSummary = $this->commissionService->getPartnerCommissionSummary($partner->id); $recentCommissions = $this->commissionService->getRecentCommissions($partner->id, 5); } return compact('commissionSummary', 'recentCommissions', 'partner'); } /** * 영업파트너 가이드북 도움말 모달 */ public function helpGuide(): View { // 가이드북 마크다운 파일 읽기 (resources/markdown 폴더) $guidePath = resource_path('markdown/영업파트너가이드북.md'); if (file_exists($guidePath)) { $markdown = file_get_contents($guidePath); $htmlContent = Str::markdown($markdown); } else { $htmlContent = '

가이드북을 찾을 수 없습니다.

'; } return view('sales.dashboard.partials.help-modal', compact('htmlContent')); } }