tenantId(); // 1) 대상 유효성 & model_type 결정 $modelType = $this->resolveModelType($id, $scope); if (! $modelType) { return ['success' => false, 'message' => '대상 리소스를 찾을 수 없습니다.', 'data' => null]; } // 2) 메뉴 목록 $menus = DB::table('menus') ->select('id', 'parent_id', 'name', 'url', 'sort_order') ->orderBy('sort_order')->orderBy('id') ->get(); // 3) 권한 정의 (permissions.name = "menu:{menuId}.{action}") $perms = DB::table('permissions') ->select('id', 'name', 'guard_name') ->where('guard_name', 'api') ->where('name', 'like', 'menu:%') ->get(); $permMap = []; // [menuId][action] => ['id','guard','code'] foreach ($perms as $p) { if (preg_match('/^menu:(\d+)\.([a-z_]+)$/', $p->name, $m)) { $permMap[(int) $m[1]][$m[2]] = [ 'id' => (int) $p->id, 'guard' => $p->guard_name, 'code' => $p->name, ]; } } // 4) 대상의 허용/차단 집합 $allows = DB::table('model_has_permissions') ->where('tenant_id', $tenantId) // ★ 강제 ->where('model_type', $modelType) ->where('model_id', $id) ->pluck('permission_id')->all(); $allowSet = array_fill_keys($allows, true); $denies = DB::table('permission_overrides') ->where('tenant_id', $tenantId) // ★ 강제 ->where('model_type', $modelType) ->where('model_id', $id) ->where('effect', -1) ->pluck('permission_id')->all(); $denySet = array_fill_keys($denies, true); // 5) 트리 + 액션 상태 구성 $actions = ['view', 'create', 'update', 'delete', 'approve']; $byId = []; foreach ($menus as $m) { $node = [ 'menu_id' => (int) $m->id, 'parent_id' => $m->parent_id ? (int) $m->parent_id : null, 'name' => $m->name, 'url' => $m->url, 'type' => 'system', 'children' => [], 'actions' => [], ]; foreach ($actions as $a) { $perm = $permMap[$m->id][$a] ?? null; if ($perm) { $pid = $perm['id']; $state = isset($denySet[$pid]) ? 'deny' : (isset($allowSet[$pid]) ? 'allow' : 'none'); $node['actions'][$a] = [ 'permission_id' => $pid, 'permission_code' => $perm['code'], 'guard_name' => $perm['guard'], 'state' => $state, 'is_allowed' => $state === 'allow' ? 1 : 0, ]; } else { $node['actions'][$a] = null; } } $byId[$m->id] = $node; } // 트리 결합 $roots = []; foreach ($byId as $key => &$node) { if ($node['parent_id'] && isset($byId[$node['parent_id']])) { $byId[$node['parent_id']]['children'][] = &$node; } else { $roots[] = &$node; } } unset($node); return [ 'success' => true, 'message' => $this->titleByScope($scope).' 성공', 'data' => [ 'actions' => $actions, 'tree' => $roots, ], ]; } /** 스코프별 대상 존재 확인 후 model_type(FQCN)만 반환 */ private function resolveModelType(int $id, string $scope): ?string { return match ($scope) { 'department' => Department::query()->find($id) ? Department::class : null, 'role' => SpatieRole::query()->find($id) ? SpatieRole::class : null, 'user' => User::query()->find($id) ? User::class : null, default => null, }; } private function titleByScope(string $scope): string { return match ($scope) { 'department' => '부서 메뉴 권한 매트릭스 조회', 'role' => '역할 메뉴 권한 매트릭스 조회', 'user' => '유저 메뉴 권한 매트릭스 조회', default => '메뉴 권한 매트릭스 조회', }; } }