Merge remote-tracking branch 'origin/develop'

This commit is contained in:
snipe 2022-10-26 00:32:39 -07:00
commit 88ae8858e8
16 changed files with 170 additions and 32 deletions

View file

@ -27,7 +27,7 @@ class LocationsController extends Controller
$allowed_columns = [ $allowed_columns = [
'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at', 'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at',
'updated_at', 'manager_id', 'image', 'updated_at', 'manager_id', 'image',
'assigned_assets_count', 'users_count', 'assets_count', 'currency', 'ldap_ou', ]; 'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_location_count', 'rtd_assets_count', 'currency', 'ldap_ou', ];
$locations = Location::with('parent', 'manager', 'children')->select([ $locations = Location::with('parent', 'manager', 'children')->select([
'locations.id', 'locations.id',
@ -46,7 +46,8 @@ class LocationsController extends Controller
'locations.ldap_ou', 'locations.ldap_ou',
'locations.currency', 'locations.currency',
])->withCount('assignedAssets as assigned_assets_count') ])->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count') ->withCount('assets as assets_location_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('users as users_count'); ->withCount('users as users_count');
if ($request->filled('search')) { if ($request->filled('search')) {
@ -156,8 +157,10 @@ class LocationsController extends Controller
'locations.currency', 'locations.currency',
]) ])
->withCount('assignedAssets as assigned_assets_count') ->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count') ->withCount('assets as assets_location_count')
->withCount('users as users_count')->findOrFail($id); ->withCount('rtd_assets as rtd_assets_count')
->withCount('users as users_count')
->findOrFail($id);
return (new LocationsTransformer)->transformLocation($location); return (new LocationsTransformer)->transformLocation($location);
} }

View file

@ -44,7 +44,8 @@ class LocationsTransformer
'country' => ($location->country) ? e($location->country) : null, 'country' => ($location->country) ? e($location->country) : null,
'zip' => ($location->zip) ? e($location->zip) : null, 'zip' => ($location->zip) ? e($location->zip) : null,
'assigned_assets_count' => (int) $location->assigned_assets_count, 'assigned_assets_count' => (int) $location->assigned_assets_count,
'assets_count' => (int) $location->assets_count, 'assets_location_count' => (int) $location->assets_location_count,
'rtd_assets_count' => (int) $location->rtd_assets_count,
'users_count' => (int) $location->users_count, 'users_count' => (int) $location->users_count,
'currency' => ($location->currency) ? e($location->currency) : null, 'currency' => ($location->currency) ? e($location->currency) : null,
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null, 'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,

View file

@ -90,6 +90,14 @@ class Location extends SnipeModel
'parent' => ['name'], 'parent' => ['name'],
]; ];
/**
* Determine whether or not this location can be deleted
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return bool
*/
public function isDeletable() public function isDeletable()
{ {
return Gate::allows('delete', $this) return Gate::allows('delete', $this)
@ -98,12 +106,25 @@ class Location extends SnipeModel
&& ($this->users()->count() === 0); && ($this->users()->count() === 0);
} }
/**
* Establishes the user -> location relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function users() public function users()
{ {
return $this->hasMany(\App\Models\User::class, 'location_id'); return $this->hasMany(\App\Models\User::class, 'location_id');
} }
/**
* Find assets with this location as their location_id
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assets() public function assets()
{ {
return $this->hasMany(\App\Models\Asset::class, 'location_id') return $this->hasMany(\App\Models\Asset::class, 'location_id')
@ -114,6 +135,14 @@ class Location extends SnipeModel
}); });
} }
/**
* Establishes the asset -> rtd_location relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function rtd_assets() public function rtd_assets()
{ {
/* This used to have an ...->orHas() clause that referred to /* This used to have an ...->orHas() clause that referred to
@ -123,48 +152,93 @@ class Location extends SnipeModel
It is arguable that we should have a '...->whereNull('assigned_to') It is arguable that we should have a '...->whereNull('assigned_to')
bit in there, but that isn't always correct either (in the case bit in there, but that isn't always correct either (in the case
where a user has no location, for example). where a user has no location, for example).
In all likelyhood, we need to denorm an "effective_location" column
into Assets to make this slightly less miserable.
*/ */
return $this->hasMany(\App\Models\Asset::class, 'rtd_location_id'); return $this->hasMany(\App\Models\Asset::class, 'rtd_location_id');
} }
/**
* Establishes the consumable -> location relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function consumables() public function consumables()
{ {
return $this->hasMany(\App\Models\Consumable::class, 'location_id'); return $this->hasMany(\App\Models\Consumable::class, 'location_id');
} }
/**
* Establishes the component -> location relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function components() public function components()
{ {
return $this->hasMany(\App\Models\Component::class, 'location_id'); return $this->hasMany(\App\Models\Component::class, 'location_id');
} }
/**
* Establishes the component -> accessory relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function accessories() public function accessories()
{ {
return $this->hasMany(\App\Models\Accessory::class, 'location_id'); return $this->hasMany(\App\Models\Accessory::class, 'location_id');
} }
/**
* Find the parent of a location
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function parent() public function parent()
{ {
return $this->belongsTo(self::class, 'parent_id', 'id') return $this->belongsTo(self::class, 'parent_id', 'id')
->with('parent'); ->with('parent');
} }
/**
* Find the manager of a location
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function manager() public function manager()
{ {
return $this->belongsTo(\App\Models\User::class, 'manager_id'); return $this->belongsTo(\App\Models\User::class, 'manager_id');
} }
/**
* Find children of a location
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function children() public function children()
{ {
return $this->hasMany(self::class, 'parent_id') return $this->hasMany(self::class, 'parent_id')
->with('children'); ->with('children');
} }
// I don't think we need this anymore since we de-normed location_id in assets? /**
* Establishes the asset -> location assignment relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assignedAssets() public function assignedAssets()
{ {
return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed(); return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed();

View file

@ -51,21 +51,32 @@ class LocationPresenter extends Presenter
], ],
[ [
'field' => 'assets_count', 'field' => 'assets_location_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => trans('admin/locations/table.assets_rtd'), 'title' => trans('admin/locations/message.current_location'),
'visible' => true, 'visible' => true,
], ],
[
'field' => 'rtd_assets_count',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/hardware/form.default_location'),
'visible' => false,
],
[ [
'field' => 'assigned_assets_count', 'field' => 'assigned_assets_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => trans('admin/locations/table.assets_checkedout'), 'title' => trans('admin/locations/message.assigned_assets'),
'visible' => true, 'visible' => true,
], ],
[ [
'field' => 'users_count', 'field' => 'users_count',
'searchable' => false, 'searchable' => false,

View file

@ -6,6 +6,8 @@ return array(
'assoc_users' => 'This location is currently associated with at least one user and cannot be deleted. Please update your users to no longer reference this location and try again. ', 'assoc_users' => 'This location is currently associated with at least one user and cannot be deleted. Please update your users to no longer reference this location and try again. ',
'assoc_assets' => 'This location is currently associated with at least one asset and cannot be deleted. Please update your assets to no longer reference this location and try again. ', 'assoc_assets' => 'This location is currently associated with at least one asset and cannot be deleted. Please update your assets to no longer reference this location and try again. ',
'assoc_child_loc' => 'This location is currently the parent of at least one child location and cannot be deleted. Please update your locations to no longer reference this location and try again. ', 'assoc_child_loc' => 'This location is currently the parent of at least one child location and cannot be deleted. Please update your locations to no longer reference this location and try again. ',
'assigned_assets' => 'Assigned Assets',
'current_location' => 'Current Location',
'create' => array( 'create' => array(

View file

@ -8,7 +8,7 @@
@section('header_right') @section('header_right')
@can('create', \App\Models\Accessory::class) @can('create', \App\Models\Accessory::class)
<a href="{{ route('accessories.create') }}" class="btn btn-primary pull-right"> {{ trans('general.create') }}</a> <a href="{{ route('accessories.create') }}" accesskey="n" class="btn btn-primary pull-right"> {{ trans('general.create') }}</a>
@endcan @endcan
@stop @stop

View file

@ -8,7 +8,7 @@
@section('header_right') @section('header_right')
@can('create', \App\Models\Component::class) @can('create', \App\Models\Component::class)
<a href="{{ route('components.create') }}" class="btn btn-primary pull-right"> {{ trans('general.create') }}</a> <a href="{{ route('components.create') }}" accesskey="n" class="btn btn-primary pull-right"> {{ trans('general.create') }}</a>
@endcan @endcan
@stop @stop

View file

@ -8,7 +8,7 @@
@section('header_right') @section('header_right')
@can('create', \App\Models\Consumable::class) @can('create', \App\Models\Consumable::class)
<a href="{{ route('consumables.create') }}" class="btn btn-primary pull-right"> {{ trans('general.create') }}</a> <a href="{{ route('consumables.create') }}" accesskey="n" class="btn btn-primary pull-right"> {{ trans('general.create') }}</a>
@endcan @endcan
@stop @stop

View file

@ -45,7 +45,7 @@
<a href="{{ route('reports/custom') }}" style="margin-right: 5px;" class="btn btn-default"> <a href="{{ route('reports/custom') }}" style="margin-right: 5px;" class="btn btn-default">
{{ trans('admin/hardware/general.custom_export') }}</a> {{ trans('admin/hardware/general.custom_export') }}</a>
@can('create', \App\Models\Asset::class) @can('create', \App\Models\Asset::class)
<a href="{{ route('hardware.create') }}" class="btn btn-primary pull-right"></i> {{ trans('general.create') }}</a> <a href="{{ route('hardware.create') }}" accesskey="n" class="btn btn-primary pull-right"></i> {{ trans('general.create') }}</a>
@endcan @endcan
@stop @stop

View file

@ -131,7 +131,7 @@
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
@can('index', \App\Models\Asset::class) @can('index', \App\Models\Asset::class)
<li aria-hidden="true"{!! (Request::is('hardware*') ? ' class="active"' : '') !!} tabindex="-1"> <li aria-hidden="true"{!! (Request::is('hardware*') ? ' class="active"' : '') !!} tabindex="-1">
<a href="{{ url('hardware') }}" tabindex="-1"> <a href="{{ url('hardware') }}" accesskey="1" tabindex="-1">
<i class="fas fa-barcode fa-fw" aria-hidden="true"></i> <i class="fas fa-barcode fa-fw" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.assets') }}</span> <span class="sr-only">{{ trans('general.assets') }}</span>
</a> </a>
@ -139,7 +139,7 @@
@endcan @endcan
@can('view', \App\Models\License::class) @can('view', \App\Models\License::class)
<li aria-hidden="true"{!! (Request::is('licenses*') ? ' class="active"' : '') !!} tabindex="-1"> <li aria-hidden="true"{!! (Request::is('licenses*') ? ' class="active"' : '') !!} tabindex="-1">
<a href="{{ route('licenses.index') }}" tabindex="-1"> <a href="{{ route('licenses.index') }}" accesskey="2" tabindex="-1">
<i class="far fa-save fa-fw"></i> <i class="far fa-save fa-fw"></i>
<span class="sr-only">{{ trans('general.licenses') }}</span> <span class="sr-only">{{ trans('general.licenses') }}</span>
</a> </a>
@ -147,7 +147,7 @@
@endcan @endcan
@can('index', \App\Models\Accessory::class) @can('index', \App\Models\Accessory::class)
<li aria-hidden="true"{!! (Request::is('accessories*') ? ' class="active"' : '') !!} tabindex="-1"> <li aria-hidden="true"{!! (Request::is('accessories*') ? ' class="active"' : '') !!} tabindex="-1">
<a href="{{ route('accessories.index') }}" tabindex="-1"> <a href="{{ route('accessories.index') }}" accesskey="3" tabindex="-1">
<i class="far fa-keyboard fa-fw"></i> <i class="far fa-keyboard fa-fw"></i>
<span class="sr-only">{{ trans('general.accessories') }}</span> <span class="sr-only">{{ trans('general.accessories') }}</span>
</a> </a>
@ -155,7 +155,7 @@
@endcan @endcan
@can('index', \App\Models\Consumable::class) @can('index', \App\Models\Consumable::class)
<li aria-hidden="true"{!! (Request::is('consumables*') ? ' class="active"' : '') !!}> <li aria-hidden="true"{!! (Request::is('consumables*') ? ' class="active"' : '') !!}>
<a href="{{ url('consumables') }}" tabindex="-1"> <a href="{{ url('consumables') }}" accesskey="4" tabindex="-1">
<i class="fas fa-tint fa-fw"></i> <i class="fas fa-tint fa-fw"></i>
<span class="sr-only">{{ trans('general.consumables') }}</span> <span class="sr-only">{{ trans('general.consumables') }}</span>
</a> </a>
@ -163,7 +163,7 @@
@endcan @endcan
@can('view', \App\Models\Component::class) @can('view', \App\Models\Component::class)
<li aria-hidden="true"{!! (Request::is('components*') ? ' class="active"' : '') !!}> <li aria-hidden="true"{!! (Request::is('components*') ? ' class="active"' : '') !!}>
<a href="{{ route('components.index') }}" tabindex="-1"> <a href="{{ route('components.index') }}" accesskey="5" tabindex="-1">
<i class="far fa-hdd fa-fw"></i> <i class="far fa-hdd fa-fw"></i>
<span class="sr-only">{{ trans('general.components') }}</span> <span class="sr-only">{{ trans('general.components') }}</span>
</a> </a>
@ -571,7 +571,7 @@
@can('view', \App\Models\User::class) @can('view', \App\Models\User::class)
<li{!! (Request::is('users*') ? ' class="active"' : '') !!}> <li{!! (Request::is('users*') ? ' class="active"' : '') !!}>
<a href="{{ route('users.index') }}"> <a href="{{ route('users.index') }}" accesskey="6">
<i class="fas fa-users fa-fw"></i> <i class="fas fa-users fa-fw"></i>
<span>{{ trans('general.people') }}</span> <span>{{ trans('general.people') }}</span>
</a> </a>

View file

@ -9,7 +9,7 @@
@section('header_right') @section('header_right')
@can('create', \App\Models\License::class) @can('create', \App\Models\License::class)
<a href="{{ route('licenses.create') }}" class="btn btn-primary pull-right"> <a href="{{ route('licenses.create') }}" accesskey="n" class="btn btn-primary pull-right">
{{ trans('general.create') }} {{ trans('general.create') }}
</a> </a>
@endcan @endcan

View file

@ -37,12 +37,13 @@
<i class="fas fa-barcode fa-2x" aria-hidden="true"></i> <i class="fas fa-barcode fa-2x" aria-hidden="true"></i>
</span> </span>
<span class="hidden-xs hidden-sm"> <span class="hidden-xs hidden-sm">
{{ trans('general.assets') }} {{ trans('admin/locations/message.current_location') }}
{!! (($location->assets) && ($location->assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->assets()->AssetsForShow()->count()).'</badge>' : '' !!} {!! (($location->assets) && ($location->assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span> </span>
</a> </a>
</li> </li>
<li> <li>
<a href="#rtd_assets" data-toggle="tab"> <a href="#rtd_assets" data-toggle="tab">
<span class="hidden-lg hidden-md"> <span class="hidden-lg hidden-md">
@ -55,6 +56,18 @@
</a> </a>
</li> </li>
<li>
<a href="#assets_assigned" data-toggle="tab">
<span class="hidden-lg hidden-md">
<i class="fas fa-barcode fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">
{{ trans('admin/locations/message.assigned_assets') }}
{!! (($location->rtd_assets) && ($location->assignedAssets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->assignedAssets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
</li>
<li> <li>
<a href="#accessories" data-toggle="tab"> <a href="#accessories" data-toggle="tab">
@ -127,7 +140,7 @@
</div><!-- /.tab-pane --> </div><!-- /.tab-pane -->
<div class="tab-pane" id="assets"> <div class="tab-pane" id="assets">
<h2 class="box-title">{{ trans('general.assets') }}</h2> <h2 class="box-title">{{ trans('admin/locations/message.current_location') }}</h2>
<div class="table table-responsive"> <div class="table table-responsive">
@include('partials.asset-bulk-actions') @include('partials.asset-bulk-actions')
@ -158,6 +171,40 @@
</div><!-- /.table-responsive --> </div><!-- /.table-responsive -->
</div><!-- /.tab-pane --> </div><!-- /.tab-pane -->
<div class="tab-pane" id="assets_assigned">
<h2 class="box-title">
{{ trans('admin/locations/message.assigned_assets') }}
</h2>
<div class="table table-responsive">
@include('partials.asset-bulk-actions', ['id_divname' => 'AssignedAssetsBulkEditToolbar', 'id_formname' => 'assignedAssetsBulkForm', 'id_button' => 'AssignedbulkAssetEditButton'])
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-cookie-id-table="assetsAssignedListingTable"
data-pagination="true"
data-id-table="assetsAssignedListingTable"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
data-show-export="true"
data-show-refresh="true"
data-sort-order="asc"
data-toolbar="#AssignedAssetsBulkEditToolbar"
data-bulk-button-id="#AssignedbulkAssetEditButton"
data-bulk-form-id="#assignedAssetsBulkForm"
data-click-to-select="true"
id="assetsListingTable"
class="table table-striped snipe-table"
data-url="{{route('api.assets.index', ['assigned_to' => $location->id, 'assigned_type' => \App\Models\Location::class]) }}"
data-export-options='{
"fileName": "export-locations-{{ str_slug($location->name) }}-assets-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div><!-- /.table-responsive -->
</div><!-- /.tab-pane -->
<div class="tab-pane" id="rtd_assets"> <div class="tab-pane" id="rtd_assets">
<h2 class="box-title">{{ trans('admin/hardware/form.default_location') }}</h2> <h2 class="box-title">{{ trans('admin/hardware/form.default_location') }}</h2>

View file

@ -19,7 +19,7 @@
@can('delete', \App\Models\Asset::class) @can('delete', \App\Models\Asset::class)
<option value="delete">{{ trans('button.delete') }}</option> <option value="delete">{{ trans('button.delete') }}</option>
@endcan @endcan
<option value="labels">{{ trans_choice('button.generate_labels', 2) }}</option> <option value="labels" accesskey="l">{{ trans_choice('button.generate_labels', 2) }}</option>
</select> </select>
<button class="btn btn-primary" id="{{ (isset($id_button)) ? $id_button : 'bulkAssetEditButton' }}" disabled>{{ trans('button.go') }}</button> <button class="btn btn-primary" id="{{ (isset($id_button)) ? $id_button : 'bulkAssetEditButton' }}" disabled>{{ trans('button.go') }}</button>

View file

@ -2,6 +2,6 @@
<div class="box-footer text-right"> <div class="box-footer text-right">
<a class="btn btn-link text-left" href="{{ URL::previous() }}">{{ trans('button.cancel') }}</a> <a class="btn btn-link text-left" href="{{ URL::previous() }}">{{ trans('button.cancel') }}</a>
<button type="submit" class="btn btn-primary"><i class="fas fa-check icon-white" aria-hidden="true"></i> {{ trans('general.save') }}</button> <button type="submit" accesskey="s" class="btn btn-primary"><i class="fas fa-check icon-white" aria-hidden="true"></i> {{ trans('general.save') }}</button>
</div> </div>
<!-- / partials/forms/edit/submit.blade.php --> <!-- / partials/forms/edit/submit.blade.php -->

View file

@ -568,7 +568,7 @@
</div><!-- /.tab-pane --> </div><!-- /.tab-pane -->
</div><!-- /.tab-content --> </div><!-- /.tab-content -->
<div class="box-footer text-right"> <div class="box-footer text-right">
<button type="submit" class="btn btn-primary"><i class="fas fa-check icon-white" aria-hidden="true"></i> {{ trans('general.save') }}</button> <button type="submit" accesskey="s" class="btn btn-primary"><i class="fas fa-check icon-white" aria-hidden="true"></i> {{ trans('general.save') }}</button>
</div> </div>
</div><!-- nav-tabs-custom --> </div><!-- nav-tabs-custom -->
</form> </form>

View file

@ -18,7 +18,7 @@
@if ($snipeSettings->ldap_enabled == 1) @if ($snipeSettings->ldap_enabled == 1)
<a href="{{ route('ldap/user') }}" class="btn btn-default pull-right"><span class="fas fa-sitemap"></span>{{trans('general.ldap_sync')}}</a> <a href="{{ route('ldap/user') }}" class="btn btn-default pull-right"><span class="fas fa-sitemap"></span>{{trans('general.ldap_sync')}}</a>
@endif @endif
<a href="{{ route('users.create') }}" class="btn btn-primary pull-right" style="margin-right: 5px;"> {{ trans('general.create') }}</a> <a href="{{ route('users.create') }}" accesskey="n" class="btn btn-primary pull-right" style="margin-right: 5px;"> {{ trans('general.create') }}</a>
@endcan @endcan
@if (request('status')=='deleted') @if (request('status')=='deleted')