diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
index b652455a57..d2cd2f1997 100644
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -352,6 +352,7 @@ class SettingsController extends Controller
$setting->dash_chart_type = $request->input('dash_chart_type');
$setting->profile_edit = $request->input('profile_edit', 0);
$setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0);
+ $setting->manager_view_enabled = $request->input('manager_view_enabled', 0);
if ($request->input('per_page') != '') {
diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php
index bbff6ba4f7..c4e72971b4 100755
--- a/app/Http/Controllers/ViewAssetsController.php
+++ b/app/Http/Controllers/ViewAssetsController.php
@@ -27,50 +27,126 @@ use Exception;
class ViewAssetsController extends Controller
{
/**
- * Redirect to the profile page.
+ * Extract custom fields that should be displayed in user view.
+ *
+ * @param User $user
+ * @return array
+ */
+ private function extractCustomFields(User $user): array
+ {
+ $fieldArray = [];
+ foreach ($user->assets as $asset) {
+ if ($asset->model && $asset->model->fieldset) {
+ foreach ($asset->model->fieldset->fields as $field) {
+ if ($field->display_in_user_view == '1') {
+ $fieldArray[$field->db_column] = $field->name;
+ }
+ }
+ }
+ }
+ return array_unique($fieldArray);
+ }
+
+ /**
+ * Get list of users viewable by the current user.
+ *
+ * @param User $authUser
+ * @return \Illuminate\Support\Collection
+ */
+ private function getViewableUsers(User $authUser): \Illuminate\Support\Collection
+ {
+ // SuperAdmin sees all users
+ if ($authUser->isSuperUser()) {
+ return User::select('id', 'first_name', 'last_name', 'username')
+ ->where('activated', 1)
+ ->orderBy('last_name')
+ ->orderBy('first_name')
+ ->get();
+ }
+
+ // Regular manager sees only their subordinates + self
+ $managedUsers = $authUser->getAllSubordinates();
+
+ // If user has subordinates, show them with self at beginning
+ if ($managedUsers->count() > 0) {
+ return collect([$authUser])->merge($managedUsers)
+ ->sortBy('last_name')
+ ->sortBy('first_name');
+ }
+
+ // User has no subordinates, only sees themselves
+ return collect([$authUser]);
+ }
+
+ /**
+ * Get the selected user ID from request or default to current user.
+ *
+ * @param Request $request
+ * @param \Illuminate\Support\Collection $subordinates
+ * @param int $defaultUserId
+ * @return int
+ */
+ private function getSelectedUserId(Request $request, \Illuminate\Support\Collection $subordinates, int $defaultUserId): int
+ {
+ // If no subordinates or no user_id in request, return default
+ if ($subordinates->count() <= 1 || !$request->filled('user_id')) {
+ return $defaultUserId;
+ }
+
+ $requestedUserId = (int) $request->input('user_id');
+
+ // Validate if the requested user is allowed
+ if ($subordinates->contains('id', $requestedUserId)) {
+ return $requestedUserId;
+ }
+
+ // If invalid ID or not authorized, return default
+ return $defaultUserId;
+ }
+
+ /**
+ * Show user's assigned assets with optional manager view functionality.
*
*/
- public function getIndex() : View | RedirectResponse
+ public function getIndex(Request $request) : View | RedirectResponse
{
- $user = User::with(
+ $authUser = auth()->user();
+ $settings = Setting::getSettings();
+ $subordinates = collect();
+ $selectedUserId = $authUser->id;
+
+ // Process manager view if enabled
+ if ($settings->manager_view_enabled) {
+ $subordinates = $this->getViewableUsers($authUser);
+ $selectedUserId = $this->getSelectedUserId($request, $subordinates, $authUser->id);
+ }
+
+ // Load the data for the user to be viewed (either auth user or selected subordinate)
+ $userToView = User::with([
'assets',
'assets.model',
'assets.model.fieldset.fields',
'consumables',
'accessories',
- 'licenses',
- )->find(auth()->id());
-
- $field_array = array();
-
- // Loop through all the custom fields that are applied to any model the user has assigned
- foreach ($user->assets as $asset) {
-
- // Make sure the model has a custom fieldset before trying to loop through the associated fields
- if ($asset->model->fieldset) {
-
- foreach ($asset->model->fieldset->fields as $field) {
- // check and make sure they're allowed to see the value of the custom field
- if ($field->display_in_user_view == '1') {
- $field_array[$field->db_column] = $field->name;
- }
-
- }
- }
+ 'licenses'
+ ])->find($selectedUserId);
+ // If the user to view couldn't be found (shouldn't happen with proper logic), redirect with error
+ if (!$userToView) {
+ return redirect()->route('view-assets')->with('error', trans('admin/users/message.user_not_found'));
}
- // Since some models may re-use the same fieldsets/fields, let's make the array unique so we don't repeat columns
- array_unique($field_array);
+ // Process custom fields for the user being viewed
+ $fieldArray = $this->extractCustomFields($userToView);
- if (isset($user->id)) {
- return view('account/view-assets', compact('user', 'field_array' ))
- ->with('settings', Setting::getSettings());
- }
-
- // Redirect to the user management page
- return redirect()->route('users.index')
- ->with('error', trans('admin/users/message.user_not_found', $user->id));
+ // Pass the necessary data to the view
+ return view('account/view-assets', [
+ 'user' => $userToView, // Use 'user' for compatibility with the existing view
+ 'field_array' => $fieldArray,
+ 'settings' => $settings,
+ 'subordinates' => $subordinates,
+ 'selectedUserId' => $selectedUserId
+ ]);
}
/**
diff --git a/app/Models/Setting.php b/app/Models/Setting.php
index 199aee33dc..73fcada1e6 100755
--- a/app/Models/Setting.php
+++ b/app/Models/Setting.php
@@ -67,11 +67,13 @@ class Setting extends Model
'google_login',
'google_client_id',
'google_client_secret',
+ 'manager_view_enabled',
];
protected $casts = [
'label2_asset_logo' => 'boolean',
'require_checkinout_notes' => 'boolean',
+ 'manager_view_enabled' => 'boolean',
];
/**
diff --git a/app/Models/User.php b/app/Models/User.php
index 278c801bed..f6bbcfaded 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -975,5 +975,75 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
+ }
+
+ /**
+ * Get all direct and indirect subordinates for this user.
+ *
+ * @return \Illuminate\Support\Collection
+ */
+ public function getAllSubordinates()
+ {
+ $subordinates = collect();
+ $this->fetchSubordinatesRecursive($this, $subordinates);
+ return $subordinates->unique('id');
+ }
+
+ /**
+ * Get all direct and indirect subordinates for this user, including self.
+ *
+ * @return \Illuminate\Support\Collection
+ */
+ public function getAllSubordinatesIncludingSelf()
+ {
+ $subordinates = collect([$this]);
+ $this->fetchSubordinatesRecursive($this, $subordinates);
+ return $subordinates->unique('id');
+ }
+
+ /**
+ * Recursive helper function to fetch subordinates.
+ *
+ * @param User $manager
+ * @param \Illuminate\Support\Collection $subs
+ */
+ protected function fetchSubordinatesRecursive(User $manager, \Illuminate\Support\Collection &$subs)
+ {
+ // Eager load 'managesUsers' to prevent N+1 queries in recursion
+ $directSubordinates = $manager->managesUsers()->with('managesUsers')->get();
+
+ foreach ($directSubordinates as $directSubordinate) {
+ // Add subordinate if not already in the collection
+ if (!$subs->contains('id', $directSubordinate->id)) {
+ $subs->push($directSubordinate);
+ // Recursive call for this subordinate's subordinates
+ $this->fetchSubordinatesRecursive($directSubordinate, $subs);
+ }
+ }
+ }
+
+ /**
+ * Check if the current user is a direct or indirect manager of the given user.
+ *
+ * @param User $userToCheck
+ * @return bool
+ */
+ public function isManagerOf(User $userToCheck): bool
+ {
+ // Optimization: If it's the same user, they are not their own manager
+ if ($this->id === $userToCheck->id) {
+ return false;
+ }
+
+ // Eager load manager relationship to potentially reduce queries in the loop
+ $manager = $userToCheck->load('manager')->manager;
+ while ($manager) {
+ if ($manager->id === $this->id) {
+ return true;
+ }
+ // Move up the hierarchy (load relationship if not already loaded)
+ $manager = $manager->load('manager')->manager;
+ }
+ return false;
}
}
diff --git a/database/migrations/2025_06_03_000000_add_manager_view_enabled_to_settings_table.php b/database/migrations/2025_06_03_000000_add_manager_view_enabled_to_settings_table.php
new file mode 100644
index 0000000000..345c52a7dd
--- /dev/null
+++ b/database/migrations/2025_06_03_000000_add_manager_view_enabled_to_settings_table.php
@@ -0,0 +1,34 @@
+boolean('manager_view_enabled')
+ ->default(false)
+ ->comment('Allow managers to view assets assigned to their subordinates');
+ });
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ if (Schema::hasColumn('settings', 'manager_view_enabled')) {
+ Schema::table('settings', function (Blueprint $table) {
+ $table->dropColumn('manager_view_enabled');
+ });
+ }
+ }
+};
diff --git a/resources/lang/en-US/admin/settings/general.php b/resources/lang/en-US/admin/settings/general.php
index 8c4ae35365..149667a5ce 100644
--- a/resources/lang/en-US/admin/settings/general.php
+++ b/resources/lang/en-US/admin/settings/general.php
@@ -403,6 +403,9 @@ return [
'due_checkin_days_help' => 'How many days before the expected checkin of an asset should it be listed in the "Due for checkin" page?',
'no_groups' => 'No groups have been created yet. Visit Admin Settings > Permission Groups to add one.',
'text' => 'Text',
+ 'manager_view' => 'Manager View',
+ 'manager_view_enabled_text' => 'Enable Manager View',
+ 'manager_view_enabled_help' => 'Allow managers to view assigned items to their direct and indirect reports in their account view.',
'username_formats' => [
'username_format' => 'Username Format',
diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php
index b21caa8168..7080203967 100644
--- a/resources/lang/en-US/general.php
+++ b/resources/lang/en-US/general.php
@@ -323,6 +323,9 @@ return [
'viewall' => 'View All',
'viewassets' => 'View Assigned Assets',
'viewassetsfor' => 'View Assets for :name',
+ 'view_user_assets' => 'View User Assets',
+ 'select_user' => 'Select User',
+ 'me' => 'Me',
'website' => 'Website',
'welcome' => 'Welcome, :name',
'years' => 'years',
diff --git a/resources/views/account/view-assets.blade.php b/resources/views/account/view-assets.blade.php
index 6ad355e04c..9bc2d80f57 100755
--- a/resources/views/account/view-assets.blade.php
+++ b/resources/views/account/view-assets.blade.php
@@ -9,6 +9,31 @@
{{-- Account page content --}}
@section('content')
+{{-- Manager View Dropdown --}}
+@if (isset($settings) && $settings->manager_view_enabled && isset($subordinates) && $subordinates->count() > 1)
+
{{ trans('admin/settings/general.require_checkinout_notes_help_text') }}
{{ trans('admin/settings/general.manager_view_enabled_help') }}
+ {!! $errors->first('manager_view_enabled', '') !!} +