$data['user_id'] ?? null, 'name' => $data['name'], 'email' => $data['email'], 'phone' => $data['phone'] ?? null, 'password' => Hash::make($data['password']), 'is_active' => false, // 승인 전까지 비활성 'parent_id' => $data['parent_id'] ?? null, // 추천인(유치자) 'approval_status' => 'pending', // 승인 대기 상태 'must_change_password' => true, ]); // 2. 테넌트 연결 $user->tenants()->attach($tenantId, [ 'is_active' => true, 'is_default' => true, 'joined_at' => now(), ]); // 3. 역할 할당 if (!empty($data['role_ids'])) { $this->syncRoles($user, $tenantId, $data['role_ids']); } // 4. 영업팀 부서 자동 할당 $this->assignSalesDepartment($user, $tenantId); // 5. 첨부 서류 저장 if (!empty($documents)) { $this->uploadDocuments($user, $tenantId, $documents); } return $user; }); } /** * 영업파트너 수정 */ public function updateSalesPartner(User $user, array $data, array $documents = []): User { return DB::transaction(function () use ($user, $data, $documents) { $tenantId = session('selected_tenant_id', 1); // 1. 기본 정보 업데이트 (parent_id는 변경 불가) $updateData = [ 'name' => $data['name'], 'email' => $data['email'], 'phone' => $data['phone'] ?? null, ]; // 비밀번호 변경 시에만 업데이트 if (!empty($data['password'])) { $updateData['password'] = Hash::make($data['password']); } $user->update($updateData); // 2. 역할 동기화 if (isset($data['role_ids'])) { $this->syncRoles($user, $tenantId, $data['role_ids']); } // 3. 새 첨부 서류 저장 if (!empty($documents)) { $this->uploadDocuments($user, $tenantId, $documents); } return $user->fresh(); }); } /** * 영업파트너 승인 */ public function approve(User $user, int $approverId): User { $user->update([ 'approval_status' => 'approved', 'approved_by' => $approverId, 'approved_at' => now(), 'is_active' => true, 'rejection_reason' => null, ]); return $user; } /** * 영업파트너 반려 */ public function reject(User $user, int $approverId, string $reason): User { $user->update([ 'approval_status' => 'rejected', 'approved_by' => $approverId, 'approved_at' => now(), 'rejection_reason' => $reason, 'is_active' => false, ]); return $user; } /** * 역할 위임 * * @param User $fromUser 역할을 넘기는 파트너 * @param User $toUser 역할을 받는 파트너 * @param string $roleName 위임할 역할 (manager, recruiter 등) */ public function delegateRole(User $fromUser, User $toUser, string $roleName): bool { $tenantId = session('selected_tenant_id', 1); return DB::transaction(function () use ($fromUser, $toUser, $roleName, $tenantId) { // 역할 조회 $role = Role::where('tenant_id', $tenantId) ->where('name', $roleName) ->first(); if (!$role) { throw new \InvalidArgumentException("역할을 찾을 수 없습니다: {$roleName}"); } // fromUser가 해당 역할을 가지고 있는지 확인 $hasRole = UserRole::where('user_id', $fromUser->id) ->where('tenant_id', $tenantId) ->where('role_id', $role->id) ->exists(); if (!$hasRole) { throw new \InvalidArgumentException("{$fromUser->name}님이 {$roleName} 역할을 보유하고 있지 않습니다."); } // fromUser에서 역할 제거 UserRole::where('user_id', $fromUser->id) ->where('tenant_id', $tenantId) ->where('role_id', $role->id) ->delete(); // toUser에게 역할 추가 (이미 있으면 무시) UserRole::firstOrCreate([ 'user_id' => $toUser->id, 'tenant_id' => $tenantId, 'role_id' => $role->id, ], [ 'assigned_at' => now(), ]); return true; }); } /** * 역할 부여 */ public function assignRole(User $user, string $roleName): bool { $tenantId = session('selected_tenant_id', 1); $role = Role::where('tenant_id', $tenantId) ->where('name', $roleName) ->first(); if (!$role) { return false; } UserRole::firstOrCreate([ 'user_id' => $user->id, 'tenant_id' => $tenantId, 'role_id' => $role->id, ], [ 'assigned_at' => now(), ]); return true; } /** * 역할 제거 */ public function removeRole(User $user, string $roleName): bool { $tenantId = session('selected_tenant_id', 1); $role = Role::where('tenant_id', $tenantId) ->where('name', $roleName) ->first(); if (!$role) { return false; } UserRole::where('user_id', $user->id) ->where('tenant_id', $tenantId) ->where('role_id', $role->id) ->delete(); return true; } /** * 영업팀 부서 자동 할당 */ private function assignSalesDepartment(User $user, int $tenantId): void { // "영업팀" 부서를 찾거나 생성 $salesDepartment = Department::firstOrCreate( [ 'tenant_id' => $tenantId, 'name' => '영업팀', ], [ 'code' => 'SALES', 'description' => '영업파트너 부서', 'is_active' => true, 'sort_order' => 100, 'created_by' => auth()->id(), ] ); // 사용자-부서 연결 (이미 있으면 무시) DepartmentUser::firstOrCreate( [ 'tenant_id' => $tenantId, 'department_id' => $salesDepartment->id, 'user_id' => $user->id, ], [ 'is_primary' => true, 'joined_at' => now(), 'created_by' => auth()->id(), ] ); } /** * 역할 동기화 */ public function syncRoles(User $user, int $tenantId, array $roleIds): void { // 영업 관련 역할만 처리 (다른 역할은 유지) $salesRoleIds = Role::where('tenant_id', $tenantId) ->whereIn('name', self::SALES_ROLES) ->pluck('id') ->toArray(); // 제거할 역할 soft delete (새 역할 목록에 없는 것들) $roleIdsToRemove = array_diff($salesRoleIds, $roleIds); if (!empty($roleIdsToRemove)) { UserRole::where('user_id', $user->id) ->where('tenant_id', $tenantId) ->whereIn('role_id', $roleIdsToRemove) ->delete(); } // 새 역할 추가 (이미 있으면 복원, 없으면 생성) foreach ($roleIds as $roleId) { // soft delete된 레코드 포함하여 검색 $existingRole = UserRole::withTrashed() ->where('user_id', $user->id) ->where('tenant_id', $tenantId) ->where('role_id', $roleId) ->first(); if ($existingRole) { // 이미 존재하면 복원 (soft delete된 경우) if ($existingRole->trashed()) { $existingRole->restore(); } } else { // 새로 생성 UserRole::create([ 'user_id' => $user->id, 'tenant_id' => $tenantId, 'role_id' => $roleId, 'assigned_at' => now(), ]); } } } /** * 서류 업로드 */ public function uploadDocuments(User $user, int $tenantId, array $documents): array { $uploaded = []; foreach ($documents as $doc) { if (!isset($doc['file']) || !$doc['file'] instanceof UploadedFile) { continue; } $file = $doc['file']; $documentType = $doc['document_type'] ?? 'other'; $description = $doc['description'] ?? null; // 파일 저장 $storedName = Str::uuid() . '.' . $file->getClientOriginalExtension(); $filePath = "sales-partners/{$user->id}/{$storedName}"; Storage::disk('tenant')->put($filePath, file_get_contents($file)); // DB 저장 $document = SalesManagerDocument::create([ 'tenant_id' => $tenantId, 'user_id' => $user->id, 'file_path' => $filePath, 'original_name' => $file->getClientOriginalName(), 'stored_name' => $storedName, 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'document_type' => $documentType, 'description' => $description, 'uploaded_by' => auth()->id(), ]); $uploaded[] = $document; } return $uploaded; } /** * 서류 삭제 */ public function deleteDocument(SalesManagerDocument $document): bool { // 물리 파일 삭제 if ($document->existsInStorage()) { Storage::disk('tenant')->delete($document->file_path); } // DB 삭제 $document->deleted_by = auth()->id(); $document->save(); return $document->delete(); } /** * 영업파트너 목록 조회 */ public function getSalesPartners(array $filters = []) { $tenantId = session('selected_tenant_id', 1); // 영업 관련 역할을 가진 사용자 조회 (비활성 사용자도 포함 - 관리 목적) $query = User::query() ->whereHas('userRoles', function ($q) use ($tenantId) { $q->where('tenant_id', $tenantId) ->whereHas('role', function ($rq) { $rq->whereIn('name', self::SALES_ROLES); }); }) ->with(['parent', 'userRoles.role', 'salesDocuments']); // 검색 if (!empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('user_id', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%") ->orWhere('phone', 'like', "%{$search}%"); }); } // 역할 필터 if (!empty($filters['role'])) { $roleName = $filters['role']; $query->whereHas('userRoles', function ($q) use ($tenantId, $roleName) { $q->where('tenant_id', $tenantId) ->whereHas('role', function ($rq) use ($roleName) { $rq->where('name', $roleName); }); }); } // 승인 상태 필터 if (!empty($filters['approval_status'])) { $query->where('approval_status', $filters['approval_status']); } // 활성 상태 필터 (지정 시에만 적용) if (isset($filters['is_active'])) { $query->where('is_active', $filters['is_active']); } // 유치자(추천인) 필터 - 현재 로그인한 사용자가 유치한 파트너만 if (!empty($filters['parent_id'])) { $query->where('parent_id', $filters['parent_id']); } return $query->orderBy('name'); } /** * 추천인(유치자) 후보 목록 * 승인된 모든 영업파트너가 추천인이 될 수 있음 */ public function getRecommenderCandidates(?int $excludeId = null) { $tenantId = session('selected_tenant_id', 1); $query = User::query() ->where('is_active', true) ->where('approval_status', 'approved') ->whereHas('userRoles', function ($q) use ($tenantId) { $q->where('tenant_id', $tenantId) ->whereHas('role', function ($rq) { $rq->whereIn('name', self::SALES_ROLES); }); }); if ($excludeId) { $query->where('id', '!=', $excludeId); } return $query->orderBy('name')->get(); } /** * 역할 위임 가능한 하위 파트너 목록 */ public function getDelegationCandidates(User $user) { return User::query() ->where('parent_id', $user->id) ->where('is_active', true) ->where('approval_status', 'approved') ->orderBy('name') ->get(); } /** * 영업 역할 목록 조회 */ public function getSalesRoles() { $tenantId = session('selected_tenant_id', 1); return Role::where('tenant_id', $tenantId) ->whereIn('name', self::SALES_ROLES) ->get(); } /** * 통계 조회 */ public function getStats(?int $parentId = null): array { $tenantId = session('selected_tenant_id', 1); $baseQuery = User::query() ->whereHas('userRoles', function ($q) use ($tenantId) { $q->where('tenant_id', $tenantId) ->whereHas('role', function ($rq) { $rq->whereIn('name', self::SALES_ROLES); }); }); // 유치자 기준 필터 if ($parentId) { $baseQuery->where('parent_id', $parentId); } return [ 'total' => (clone $baseQuery)->count(), 'pending' => (clone $baseQuery)->where('approval_status', 'pending')->count(), 'approved' => (clone $baseQuery)->where('approval_status', 'approved')->count(), 'sales' => (clone $baseQuery) ->whereHas('userRoles.role', fn($q) => $q->where('name', 'sales')) ->count(), 'manager' => (clone $baseQuery) ->whereHas('userRoles.role', fn($q) => $q->where('name', 'manager')) ->count(), ]; } /** * 승인 대기 통계 조회 (본사 관리자용) */ public function getApprovalStats(): array { $tenantId = session('selected_tenant_id', 1); $baseQuery = User::query() ->whereHas('userRoles', function ($q) use ($tenantId) { $q->where('tenant_id', $tenantId) ->whereHas('role', function ($rq) { $rq->whereIn('name', self::SALES_ROLES); }); }); return [ 'pending' => (clone $baseQuery)->where('approval_status', 'pending')->count(), 'approved_today' => (clone $baseQuery) ->where('approval_status', 'approved') ->whereDate('approved_at', today()) ->count(), 'rejected_today' => (clone $baseQuery) ->where('approval_status', 'rejected') ->whereDate('approved_at', today()) ->count(), ]; } /** * 파트너의 계층 레벨 계산 */ public function getPartnerLevel(User $user): int { $level = 1; $current = $user; while ($current->parent_id !== null) { $level++; $current = $current->parent; // 무한 루프 방지 if ($level > 100) { break; } } return $level; } /** * 하위 파트너 전체 목록 (재귀) */ public function getAllDescendants(User $user, int $maxDepth = 10): array { $descendants = []; $this->collectDescendants($user, $descendants, 1, $maxDepth); return $descendants; } private function collectDescendants(User $user, array &$descendants, int $currentDepth, int $maxDepth): void { if ($currentDepth > $maxDepth) { return; } $children = User::where('parent_id', $user->id) ->where('approval_status', 'approved') ->get(); foreach ($children as $child) { $descendants[] = [ 'user' => $child, 'level' => $currentDepth, ]; $this->collectDescendants($child, $descendants, $currentDepth + 1, $maxDepth); } } }