withCount(['users', 'departments', 'menus', 'roles']) ->withTrashed(); // 검색 필터 if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('company_name', 'like', "%{$search}%") ->orWhere('code', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%"); }); } // 상태 필터 if (! empty($filters['tenant_st_code'])) { $query->where('tenant_st_code', $filters['tenant_st_code']); } // Soft Delete 필터 if (isset($filters['trashed'])) { if ($filters['trashed'] === 'only') { $query->onlyTrashed(); } elseif ($filters['trashed'] === 'with') { $query->withTrashed(); } } // 정렬 $sortBy = $filters['sort_by'] ?? 'id'; $sortDirection = $filters['sort_direction'] ?? 'desc'; $query->orderBy($sortBy, $sortDirection); return $query->paginate($perPage); } /** * 특정 테넌트 조회 */ public function getTenantById(int $id, bool $withTrashed = false): ?Tenant { $query = Tenant::query()->withCount(['users', 'departments', 'menus', 'roles']); if ($withTrashed) { $query->withTrashed(); } return $query->find($id); } /** * 테넌트 생성 */ public function createTenant(array $data): Tenant { return Tenant::create($data); } /** * 테넌트 수정 */ public function updateTenant(int $id, array $data): bool { $tenant = Tenant::findOrFail($id); return $tenant->update($data); } /** * 테넌트 삭제 (Soft Delete) */ public function deleteTenant(int $id): bool { $tenant = Tenant::findOrFail($id); // 삭제자 기록 $tenant->deleted_by = auth()->id(); $tenant->save(); return $tenant->delete(); } /** * 테넌트 복원 */ public function restoreTenant(int $id): bool { $tenant = Tenant::onlyTrashed()->findOrFail($id); // 삭제 정보 초기화 $tenant->deleted_by = null; return $tenant->restore(); } /** * 테넌트 영구 삭제 (슈퍼관리자 전용) * * 1. 테넌트와 관련 데이터를 아카이브에 저장 * 2. 관련 데이터 삭제 * 3. 테넌트 영구 삭제 */ public function forceDeleteTenant(int $id): bool { $tenant = Tenant::withTrashed()->findOrFail($id); return DB::transaction(function () use ($tenant) { // 1. 아카이브에 저장 (복원 가능하도록) $this->archiveService->archiveTenantWithRelations($tenant); // 2. 관련 데이터 삭제 $tenant->users()->detach(); // user_tenants 관계 삭제 $tenant->departments()->forceDelete(); // 부서 영구 삭제 $tenant->menus()->forceDelete(); // 메뉴 영구 삭제 $tenant->roles()->forceDelete(); // 역할 영구 삭제 // 3. 테넌트 영구 삭제 return $tenant->forceDelete(); }); } /** * 활성 테넌트 목록 (드롭다운용) */ public function getActiveTenants(): Collection { return Tenant::query() ->active() ->orderBy('company_name') ->get(['id', 'company_name', 'code']); } /** * 테넌트 코드 중복 체크 */ public function isCodeExists(string $code, ?int $excludeId = null): bool { $query = Tenant::where('code', $code); if ($excludeId) { $query->where('id', '!=', $excludeId); } return $query->exists(); } /** * 테넌트 통계 */ public function getTenantStats(): array { return [ 'total' => Tenant::count(), 'active' => Tenant::where('tenant_st_code', 'active')->count(), 'trial' => Tenant::where('tenant_st_code', 'trial')->count(), 'suspended' => Tenant::where('tenant_st_code', 'suspended')->count(), 'expired' => Tenant::where('tenant_st_code', 'expired')->count(), 'trashed' => Tenant::onlyTrashed()->count(), ]; } /** * 모달용 테넌트 상세 정보 조회 */ public function getTenantForModal(int $id): ?Tenant { return Tenant::query() ->with('deletedByUser') ->withCount(['users', 'departments', 'menus', 'roles']) ->withTrashed() ->find($id); } /** * 테넌트 소속 사용자 목록 조회 */ public function getTenantUsers(int $tenantId): Collection { $tenant = Tenant::find($tenantId); if (! $tenant) { return collect(); } // 사용자와 함께 역할/부서 정보를 가져옴 return $tenant->users() ->with(['userRoles' => function ($query) use ($tenantId) { $query->where('tenant_id', $tenantId)->with('role'); }, 'departmentUsers' => function ($query) use ($tenantId) { $query->where('tenant_id', $tenantId)->with('department'); }]) ->orderBy('name') ->get() ->map(function ($user) { // 역할과 부서를 플랫하게 매핑 $user->roles = $user->userRoles->pluck('role')->filter(); $user->department = $user->departmentUsers->first()?->department; return $user; }); } /** * 테넌트 부서 목록 조회 (계층 구조) */ public function getTenantDepartments(int $tenantId): Collection { // Department 모델의 users() 관계가 잘못된 네임스페이스를 참조하므로 직접 카운트 $departments = \App\Models\Department::query() ->where('tenant_id', $tenantId) ->orderBy('sort_order') ->orderBy('name') ->get(); // 부서별 사용자 수 직접 카운트 $userCounts = DB::table('department_user') ->whereIn('department_id', $departments->pluck('id')) ->selectRaw('department_id, count(*) as cnt') ->groupBy('department_id') ->pluck('cnt', 'department_id'); return $departments->map(function ($dept) use ($userCounts, $departments) { // 사용자 수 설정 $dept->users_count = $userCounts[$dept->id] ?? 0; // 계층 깊이 계산 $depth = 0; $parentId = $dept->parent_id; while ($parentId) { $depth++; $parent = $departments->firstWhere('id', $parentId); $parentId = $parent?->parent_id; } $dept->depth = $depth; // 상위 부서 이름 $dept->parent = $departments->firstWhere('id', $dept->parent_id); return $dept; }); } /** * 테넌트 역할 목록 조회 */ public function getTenantRoles(int $tenantId): Collection { // Role 모델의 userRoles() 관계가 잘못된 네임스페이스를 참조하므로 직접 카운트 $roles = \App\Models\Role::query() ->where('tenant_id', $tenantId) ->withCount('permissions') ->with('permissions') ->orderBy('name') ->get(); // 역할별 사용자 수 직접 카운트 $userCounts = DB::table('user_roles') ->whereIn('role_id', $roles->pluck('id')) ->selectRaw('role_id, count(*) as cnt') ->groupBy('role_id') ->pluck('cnt', 'role_id'); return $roles->map(function ($role) use ($userCounts) { $role->users_count = $userCounts[$role->id] ?? 0; return $role; }); } /** * 테넌트 메뉴 목록 조회 (계층 구조 - 트리 정렬) */ public function getTenantMenus(int $tenantId): \Illuminate\Support\Collection { $menus = \App\Models\Commons\Menu::query() ->where('tenant_id', $tenantId) ->orderBy('sort_order') ->orderBy('id') ->get(); // 트리 구조로 정렬 후 플랫한 배열로 변환 return $this->flattenMenuTree($menus); } /** * 트리 구조를 플랫한 배열로 변환 (depth 정보 포함) */ private function flattenMenuTree(Collection $menus, ?int $parentId = null, int $depth = 0): \Illuminate\Support\Collection { $result = collect(); $filteredMenus = $menus->where('parent_id', $parentId)->sortBy('sort_order'); foreach ($filteredMenus as $menu) { $menu->depth = $depth; // 자식 메뉴 존재 여부 확인 $menu->has_children = $menus->where('parent_id', $menu->id)->count() > 0; $result->push($menu); // 자식 메뉴 재귀적으로 추가 $children = $this->flattenMenuTree($menus, $menu->id, $depth + 1); $result = $result->merge($children); } return $result; } /** * 테넌트 구독 정보 조회 */ public function getTenantSubscription(int $tenantId): object { $tenant = Tenant::find($tenantId); // 기본 구독 정보 (실제 구현 시 별도 테이블에서 조회) return (object) [ 'plan_name' => $tenant?->subscription_plan ?? '기본 플랜', 'status' => $tenant?->subscription_status ?? 'active', 'started_at' => $tenant?->created_at?->format('Y-m-d'), 'expires_at' => $tenant?->subscription_expires_at ?? null, 'next_billing_date' => null, 'max_users' => $tenant?->max_users ?? null, 'max_storage' => $tenant?->max_storage ?? null, 'max_storage_mb' => null, 'has_api_access' => true, 'has_advanced_reports' => false, 'features' => [ ['name' => '기본 기능', 'enabled' => true], ['name' => '사용자 관리', 'enabled' => true], ['name' => '부서 관리', 'enabled' => true], ['name' => '역할 관리', 'enabled' => true], ['name' => 'API 접근', 'enabled' => true], ['name' => '고급 보고서', 'enabled' => false], ], ]; } /** * 테넌트 사용량 정보 조회 */ public function getTenantUsage(int $tenantId): object { $tenant = Tenant::withCount('users')->find($tenantId); return (object) [ 'users_count' => $tenant?->users_count ?? 0, 'storage_used' => '0 MB', 'storage_used_mb' => 0, ]; } }