Merge branch 'develop' into fixes/bulk-checkin-logging

# Conflicts:
#	app/Http/Controllers/Users/BulkUsersController.php
This commit is contained in:
Marcus Moore 2024-08-07 13:06:59 -07:00
commit 41437e4d8f
No known key found for this signature in database
71 changed files with 232 additions and 102 deletions

View file

@ -1,35 +1,35 @@
FROM alpine:3.18.6
FROM alpine:3.19
# Apache + PHP
RUN apk add --no-cache \
apache2 \
php81 \
php81-common \
php81-apache2 \
php81-curl \
php81-ldap \
php81-mysqli \
php81-gd \
php81-xml \
php81-mbstring \
php81-zip \
php81-ctype \
php81-tokenizer \
php81-pdo_mysql \
php81-openssl \
php81-bcmath \
php81-phar \
php81-json \
php81-iconv \
php81-fileinfo \
php81-simplexml \
php81-session \
php81-dom \
php81-xmlwriter \
php81-xmlreader \
php81-sodium \
php81-redis \
php81-pecl-memcached \
php81-exif \
php82 \
php82-common \
php82-apache2 \
php82-curl \
php82-ldap \
php82-mysqli \
php82-gd \
php82-xml \
php82-mbstring \
php82-zip \
php82-ctype \
php82-tokenizer \
php82-pdo_mysql \
php82-openssl \
php82-bcmath \
php82-phar \
php82-json \
php82-iconv \
php82-fileinfo \
php82-simplexml \
php82-session \
php82-dom \
php82-xmlwriter \
php82-xmlreader \
php82-sodium \
php82-redis \
php82-pecl-memcached \
php82-exif \
curl \
wget \
vim \
@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite

View file

@ -248,6 +248,7 @@ class LocationsController extends Controller
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withCount('accessories as accessories_count')
->findOrFail($id);
if (! $location->isDeletable()) {

View file

@ -20,7 +20,7 @@ trait CheckInOutRequest
return Location::findOrFail(request('assigned_location'));
case 'asset':
return Asset::findOrFail(request('assigned_asset'));
case 'user':
default:
return User::findOrFail(request('assigned_user'));
}

View file

@ -8,6 +8,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
@ -97,6 +98,10 @@ class ComponentCheckoutController extends Controller
// Check if the asset exists
$asset = Asset::find($request->input('asset_id'));
if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) {
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
}
// Update the component data
$component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [

View file

@ -324,6 +324,7 @@ class SettingsController extends Controller
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->unique_serial = $request->input('unique_serial', '0');
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
$setting->show_archived_in_list = $request->input('show_archived_in_list', '0');
$setting->dashboard_message = $request->input('dashboard_message');

View file

@ -219,8 +219,8 @@ class BulkUsersController extends Controller
}
$users = User::whereIn('id', $user_raw_array)->get();
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get();
$accessoryUserRows = DB::table('accessories_checkout')->whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get();
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', User::class)->get();
$accessoryUserRows = DB::table('accessories_checkout')->whereIn('assigned_to', $user_raw_array)->where('assigned_type', User::class)->get();
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
$consumableUserRows = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();

View file

@ -205,7 +205,11 @@ class Component extends SnipeModel
public function numCheckedOut()
{
$checkedout = 0;
foreach ($this->assets as $checkout) {
// In case there are elements checked out to assets that belong to a different company
// than this asset and full multiple company support is on we'll remove the global scope,
// so they are included in the count.
foreach ($this->assets()->withoutGlobalScope(new CompanyableScope)->get() as $checkout) {
$checkedout += $checkout->pivot->assigned_qty;
}

View file

@ -108,10 +108,11 @@ class Location extends SnipeModel
{
return Gate::allows('delete', $this)
&& ($this->assets_count === 0)
&& ($this->assigned_assets_count === 0)
&& ($this->children_count === 0)
&& ($this->users_count === 0);
&& ($this->assets_count == 0)
&& ($this->assigned_assets_count == 0)
&& ($this->children_count == 0)
&& ($this->accessories_count == 0)
&& ($this->users_count == 0);
}
/**

View file

@ -6,5 +6,5 @@ return array (
'prerelease_version' => '',
'hash_version' => 'gc2bcc2e2d',
'full_hash' => 'v7.0.10-311-gc2bcc2e2d',
'branch' => 'develop',
'branch' => 'master',
);

View file

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('settings', function (Blueprint $table) {
$table->boolean('shortcuts_enabled')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('shortcuts_enabled');
});
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/js/dist/all.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -1,24 +1,24 @@
{
"/js/build/app.js": "/js/build/app.js?id=842cc33168d973ac10d35eb664be2a2c",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374",
"/css/build/overrides.css": "/css/build/overrides.css?id=004835e70ed3ae2e2340162b7a37c752",
"/css/build/app.css": "/css/build/app.css?id=7ecac57fc8cf6fdbe447c18d5f2ae7bc",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=4ea0068716c1bb2434d87a16d51b98c9",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=4fa7aa3ba499c8f4e390eb8549da3f74",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=0640e45bad692dcf62873c6e85904899",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=393aaa7b368b0670fc42434c8cca7dc7",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=f8b26018a1533b9db864247daaf06daa",
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=6fe68325d5356197672c27bc77cedcb4",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=ad39859637dafa781288630f9d6d6523",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=f0b08873a06bb54daeee176a9459f4a9",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=553ee68741b5a392037abcf04da80adc",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/all.css": "/css/dist/all.css?id=656b0a0561a4be447c195846c3de3558",
"/js/build/app.js": "/js/build/app.js?id=05dea4c19ca75f9fc68915a82c341f24",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/build/overrides.css": "/css/build/overrides.css?id=3a0b83c0a9919e91f7c795a1971382fd",
"/css/build/app.css": "/css/build/app.css?id=d6fd5ea2989e7ab278745d995c167ae7",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=a67bd93bed52e6a29967fe472de66d6c",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=268041e902b019730c23ee3875838005",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=8a12dfa3bef796fc17178890e0aff8d6",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=34023bf46b7c2486b7468de9b750dbff",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=dd5eb6c76770bacaa2e960849d275516",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=fc7adb943668ac69fe4b646625a7571f",
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=b391d15ee01e2aaffe90554eae9fde0b",
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=cf6c8c340420724b02d6e787ef9bded5",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=eb99dbccf43841b18be1b3ffc69d9497",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=5f3abb12a286d6cb8aa523322d7f053b",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=198553147983f411db55d774009bf481",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
"/css/dist/all.css": "/css/dist/all.css?id=fba2adaeb1f10de7b4f6628260ee6ef2",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
@ -94,20 +94,20 @@
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=8abbb6aea625ec64cd7ebdad77ebf6e5",
"/js/build/vendor.js": "/js/build/vendor.js?id=e27070bdbc5fce3bfd132b952d641fd6",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=859e11e4e6b05c84e4b7302de29bac5e",
"/js/dist/all.js": "/js/dist/all.js?id=01108f9d8f4f67b20669f0c25a64eb5d",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=553ee68741b5a392037abcf04da80adc",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=f0b08873a06bb54daeee176a9459f4a9",
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=f677207c6cf9678eb539abecb408c374",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=0640e45bad692dcf62873c6e85904899",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=393aaa7b368b0670fc42434c8cca7dc7",
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=ad39859637dafa781288630f9d6d6523",
"/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=6fe68325d5356197672c27bc77cedcb4",
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=f8b26018a1533b9db864247daaf06daa",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=4fa7aa3ba499c8f4e390eb8549da3f74",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da"
"/js/dist/all.js": "/js/dist/all.js?id=d4e3181b505407e7bd10b1fd802ae109",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=198553147983f411db55d774009bf481",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=5f3abb12a286d6cb8aa523322d7f053b",
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=34023bf46b7c2486b7468de9b750dbff",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=fc7adb943668ac69fe4b646625a7571f",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=dd5eb6c76770bacaa2e960849d275516",
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=eb99dbccf43841b18be1b3ffc69d9497",
"/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=cf6c8c340420724b02d6e787ef9bded5",
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=b391d15ee01e2aaffe90554eae9fde0b",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=268041e902b019730c23ee3875838005",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=8a12dfa3bef796fc17178890e0aff8d6",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=f0fbbb0ac729ea092578fb05ca615460"
}

View file

@ -218,6 +218,8 @@ return [
'webhook_integration_help' => ':app integration is optional, however the endpoint and channel are required if you wish to use it. To configure :app integration, you must first <a href=":webhook_link" target="_new" rel="noopener">create an incoming webhook</a> on your :app account. Click on the <strong>Test :app Integration</strong> button to confirm your settings are correct before saving. ',
'webhook_integration_help_button' => 'Once you have saved your :app information, a test button will appear.',
'webhook_test_help' => 'Test whether your :app integration is configured correctly. YOU MUST SAVE YOUR UPDATED :app SETTINGS FIRST.',
'shortcuts_enabled' => 'Enable Shortcuts',
'shortcuts_help_text' => '<strong>Windows</strong>: Alt + Access key, <strong>Mac</strong>: Control + Option + Access key',
'snipe_version' => 'Snipe-IT version',
'support_footer' => 'Support Footer Links ',
'support_footer_help' => 'Specify who sees the links to the Snipe-IT Support info and Users Manual',

View file

@ -66,14 +66,8 @@
</div>
<!-- User -->
@include ('partials.forms.checkout-selector', ['user_select' => 'true','asset_select' => 'true', 'location_select' => 'true'])
@include ('partials.forms.edit.user-select', ['translated_name' => trans('general.select_user'), 'fieldname' => 'assigned_user', 'required'=> 'true'])
<!-- We have to pass unselect here so that we don't default to the asset that's being checked out. We want that asset to be pre-selected everywhere else. -->
@include ('partials.forms.edit.asset-select', ['translated_name' => trans('general.asset'), 'fieldname' => 'assigned_asset', 'unselect' => 'true', 'style' => 'display:none;', 'required'=>'true'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'assigned_location', 'style' => 'display:none;', 'required'=>'true'])
<!-- Checkout QTY -->
<div class="form-group {{ $errors->has('checkout_qty') ? 'error' : '' }} ">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,7 +26,7 @@
@can('delete', \App\Models\Asset::class)
<option value="delete">{{ trans('button.delete') }}</option>
@endcan
<option value="labels" accesskey="l">{{ trans_choice('button.generate_labels', 2) }}</option>
<option value="labels" {{$snipeSettings->shortcuts_enabled == 1 ? "accesskey=l" : ''}}>{{ trans_choice('button.generate_labels', 2) }}</option>
@endif
</select>

View file

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

View file

@ -163,6 +163,21 @@
</div>
</div>
<!-- Shortcuts enable -->
<div class="form-group {{ $errors->has('shortcuts_enabled') ? 'error' : '' }}">
<div class="col-md-3">
<strong> {{ trans('admin/settings/general.shortcuts_enabled') }}</strong>
</div>
<div class="col-md-9">
<label class="form-control">
<input type="checkbox" name="shortcuts_enabled" value="1" {{ old('shortcuts_enabled', $setting->shortcuts_enabled) ? 'checked' : '' }}>
{{ trans('general.yes') }}
</label>
{!! $errors->first('shortcuts_enabled', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
<p class="help-block">{!!trans('admin/settings/general.shortcuts_help_text') !!}</p>
</div>
</div>
<!-- Per Page -->
<div class="form-group {{ $errors->has('per_page') ? 'error' : '' }}">

View file

@ -18,7 +18,7 @@
@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>
@endif
<a href="{{ route('users.create') }}" accesskey="n" class="btn btn-primary pull-right" style="margin-right: 5px;"> {{ trans('general.create') }}</a>
<a href="{{ route('users.create') }}" {{$snipeSettings->shortcuts_enabled == 1 ? "n" : ''}} class="btn btn-primary pull-right" style="margin-right: 5px;"> {{ trans('general.create') }}</a>
@endcan
@if (request('status')=='deleted')

View file

@ -578,6 +578,8 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
Route::patch('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.update');
Route::put('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.put-update');
Route::put('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.put-update');
Route::resource('hardware',
Api\AssetsController::class,
['names' => [

View file

@ -1,6 +1,6 @@
<?php
namespace Feature\Assets\Ui;
namespace Tests\Feature\Assets\Ui;
use App\Models\Asset;
use App\Models\User;

View file

@ -1,6 +1,6 @@
<?php
namespace Feature\Assets\Ui;
namespace Tests\Feature\Assets\Ui;
use App\Models\Asset;
use App\Models\AssetModel;

View file

@ -1,10 +1,13 @@
<?php
namespace Tests\Feature\Checkins\Ui;
namespace Tests\Feature\Checkouts\Ui;
use App\Events\CheckoutableCheckedOut;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Component;
use App\Models\User;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class ComponentsCheckoutTest extends TestCase
@ -18,6 +21,27 @@ class ComponentsCheckoutTest extends TestCase
->assertForbidden();
}
public function test_cannot_checkout_across_companies_when_full_company_support_enabled()
{
Event::fake([CheckoutableCheckedOut::class]);
$this->settings->enableMultipleFullCompanySupport();
[$assetCompany, $componentCompany] = Company::factory()->count(2)->create();
$asset = Asset::factory()->for($assetCompany)->create();
$component = Component::factory()->for($componentCompany)->create();
$this->actingAs(User::factory()->superuser()->create())
->post(route('components.checkout.store', $component), [
'asset_id' => $asset->id,
'assigned_qty' => '1',
'redirect_option' => 'index',
]);
Event::assertNotDispatched(CheckoutableCheckedOut::class);
}
public function testComponentCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex()
{
$component = Component::factory()->create();
@ -63,6 +87,4 @@ class ComponentsCheckoutTest extends TestCase
->assertStatus(302)
->assertRedirect(route('hardware.show', ['hardware' => $asset]));
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\Consumables\Ui;
namespace Tests\Feature\Licenses\Ui;
use App\Models\AssetModel;
use App\Models\Category;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\Consumables\Ui;
namespace Tests\Feature\Licenses\Ui;
use App\Models\License;
use App\Models\Depreciation;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\Consumables\Api;
namespace Tests\Feature\Locations\Api;
use App\Models\Location;
use App\Models\Asset;

View file

@ -1,10 +1,12 @@
<?php
namespace Tests\Unit;
use App\Models\Asset;
use App\Models\Category;
use App\Models\Company;
use App\Models\Component;
use App\Models\Location;
use App\Models\User;
use Tests\TestCase;
class ComponentTest extends TestCase
@ -41,4 +43,58 @@ class ComponentTest extends TestCase
$this->assertInstanceOf(Category::class, $component->category);
$this->assertEquals('component', $component->category->category_type);
}
public function test_num_checked_out_takes_does_not_scope_by_company()
{
$this->settings->enableMultipleFullCompanySupport();
[$companyA, $companyB] = Company::factory()->count(2)->create();
$componentForCompanyA = Component::factory()->for($companyA)->create(['qty' => 5]);
$assetForCompanyB = Asset::factory()->for($companyB)->create();
// Ideally, we shouldn't have a component attached to an
// asset from a different company but alas...
$componentForCompanyA->assets()->attach($componentForCompanyA->id, [
'component_id' => $componentForCompanyA->id,
'assigned_qty' => 4,
'asset_id' => $assetForCompanyB->id,
]);
$this->actingAs(User::factory()->superuser()->create());
$this->assertEquals(4, $componentForCompanyA->fresh()->numCheckedOut());
$this->actingAs(User::factory()->admin()->create());
$this->assertEquals(4, $componentForCompanyA->fresh()->numCheckedOut());
$this->actingAs(User::factory()->for($companyA)->create());
$this->assertEquals(4, $componentForCompanyA->fresh()->numCheckedOut());
}
public function test_num_remaining_takes_company_scoping_into_account()
{
$this->settings->enableMultipleFullCompanySupport();
[$companyA, $companyB] = Company::factory()->count(2)->create();
$componentForCompanyA = Component::factory()->for($companyA)->create(['qty' => 5]);
$assetForCompanyB = Asset::factory()->for($companyB)->create();
// Ideally, we shouldn't have a component attached to an
// asset from a different company but alas...
$componentForCompanyA->assets()->attach($componentForCompanyA->id, [
'component_id' => $componentForCompanyA->id,
'assigned_qty' => 4,
'asset_id' => $assetForCompanyB->id,
]);
$this->actingAs(User::factory()->superuser()->create());
$this->assertEquals(1, $componentForCompanyA->fresh()->numRemaining());
$this->actingAs(User::factory()->admin()->create());
$this->assertEquals(1, $componentForCompanyA->fresh()->numRemaining());
$this->actingAs(User::factory()->for($companyA)->create());
$this->assertEquals(1, $componentForCompanyA->fresh()->numRemaining());
}
}