diff --git a/app/Console/Commands/CreateAdmin.php b/app/Console/Commands/CreateAdmin.php index 5aebde3777..114f92dae4 100644 --- a/app/Console/Commands/CreateAdmin.php +++ b/app/Console/Commands/CreateAdmin.php @@ -20,13 +20,14 @@ class CreateAdmin extends Command * @property string $password * @property boolean $activated * @property boolean $show_in_list + * @property boolean $autoassign_licenses * @property \Illuminate\Support\Carbon|null $created_at * @property mixed $created_by */ - protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?}'; + protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?} {autoassign_licenses?}'; /** * The console command description. @@ -54,6 +55,9 @@ class CreateAdmin extends Command $email = $this->option('email'); $password = $this->option('password'); $show_in_list = $this->argument('show_in_list'); + $autoassign_licenses = $this->argument('autoassign_licenses'); + + if (($first_name == '') || ($last_name == '') || ($username == '') || ($email == '') || ($password == '')) { $this->info('ERROR: All fields are required.'); @@ -70,6 +74,11 @@ class CreateAdmin extends Command if ($show_in_list == 'false') { $user->show_in_list = 0; } + + if ($autoassign_licenses == 'false') { + $user->autoassign_licenses = 0; + } + if ($user->save()) { $this->info('New user created'); $user->groups()->attach(1); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 7c63bb925c..dc444ec9e9 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -71,6 +71,7 @@ class UsersController extends Controller 'users.start_date', 'users.end_date', 'users.vip', + 'users.autoassign_licenses', ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',) ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count'); @@ -187,6 +188,10 @@ class UsersController extends Controller $users->has('accessories', '=', $request->input('accessories_count')); } + if ($request->filled('autoassign_licenses')) { + $users->where('autoassign_licenses', '=', $request->input('autoassign_licenses')); + } + if ($request->filled('search')) { $users = $users->TextSearch($request->input('search')); } @@ -259,6 +264,7 @@ class UsersController extends Controller 'vip', 'start_date', 'end_date', + 'autoassign_licenses', ]; $sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name'; @@ -356,7 +362,7 @@ class UsersController extends Controller $user->permissions = $permissions_array; } - $tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20); + $tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40); $user->password = bcrypt($request->get('password', $tmp_pass)); app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index 257722b005..50e20c7985 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -112,4 +112,54 @@ class LicenseCheckinController extends Controller // Redirect to the license page with error return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkin.error')); } + + /** + * Bulk checkin all license seats + * + * @author [A. Gianotto] [] + * @see LicenseCheckinController::create() method that provides the form view + * @since [v6.1.1] + * @return \Illuminate\Http\RedirectResponse + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + + public function bulkCheckin(Request $request, $licenseId) { + + $license = License::findOrFail($licenseId); + $this->authorize('checkin', $license); + + $licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId) + ->whereNotNull('assigned_to') + ->with('user') + ->get(); + + foreach ($licenseSeatsByUser as $user_seat) { + $user_seat->assigned_to = null; + + if ($user_seat->save()) { + \Log::debug('Checking in '.$license->name.' from user '.$user_seat->username); + $user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg')); + } + } + + $licenseSeatsByAsset = LicenseSeat::where('license_id', '=', $licenseId) + ->whereNotNull('asset_id') + ->with('asset') + ->get(); + + $count = 0; + foreach ($licenseSeatsByAsset as $asset_seat) { + $asset_seat->asset_id = null; + + if ($asset_seat->save()) { + \Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag); + $asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg')); + $count++; + } + } + + return redirect()->back()->with('success', trans_choice('admin/licenses/general.bulk.checkin_all.success', 2, ['count' => $count] )); + + } + } diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 6f2ae003ca..a710497692 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -126,4 +126,70 @@ class LicenseCheckoutController extends Controller return false; } + + /** + * Bulk checkin all license seats + * + * @author [A. Gianotto] [] + * @see LicenseCheckinController::create() method that provides the form view + * @since [v6.1.1] + * @return \Illuminate\Http\RedirectResponse + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + + public function bulkCheckout($licenseId) { + + \Log::debug('Checking out '.$licenseId.' via bulk'); + $license = License::findOrFail($licenseId); + $this->authorize('checkin', $license); + $avail_count = $license->getAvailSeatsCountAttribute(); + + $users = User::whereNull('deleted_at')->where('autoassign_licenses', '=', 1)->with('licenses')->get(); + \Log::debug($avail_count.' will be assigned'); + + if ($users->count() > $avail_count) { + \Log::debug('You do not have enough free seats to complete this task, so we will check out as many as we can. '); + } + + // If the license is valid, check that there is an available seat + if ($license->availCount()->count() < 1) { + return redirect()->back()->with('error', trans('admin/licenses/general.bulk.checkout_all.error_no_seats')); + } + + + $assigned_count = 0; + + foreach ($users as $user) { + + // Check to make sure this user doesn't already have this license checked out to them + if ($user->licenses->where('id', '=', $licenseId)->count()) { + \Log::debug($user->username.' already has this license checked out to them. Skipping... '); + continue; + } + + $licenseSeat = $license->freeSeat(); + + // Update the seat with checkout info + $licenseSeat->assigned_to = $user->id; + + if ($licenseSeat->save()) { + $avail_count--; + $assigned_count++; + $licenseSeat->logCheckout(trans('admin/licenses/general.bulk.checkout_all.log_msg'), $user); + \Log::debug('License '.$license->name.' seat '.$licenseSeat->id.' checked out to '.$user->username); + } + + if ($avail_count == 0) { + return redirect()->back()->with('warning', trans('admin/licenses/general.bulk.checkout_all.warn_not_enough_seats', ['count' => $assigned_count])); + } + } + + if ($assigned_count == 0) { + return redirect()->back()->with('warning', trans('admin/licenses/general.bulk.checkout_all.warn_no_avail_users', ['count' => $assigned_count])); + } + + return redirect()->back()->with('success', trans_choice('admin/licenses/general.bulk.checkout_all.success', 2, ['count' => $assigned_count] )); + + + } } diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php index a0467654ca..a3607c1810 100755 --- a/app/Http/Controllers/Licenses/LicensesController.php +++ b/app/Http/Controllers/Licenses/LicensesController.php @@ -6,6 +6,8 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Company; use App\Models\License; +use App\Models\LicenseSeat; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -232,17 +234,39 @@ class LicensesController extends Controller public function show($licenseId = null) { $license = License::with('assignedusers')->find($licenseId); + $users_count = User::where('autoassign_licenses', '1')->count(); + $total_seats_count = $license->totalSeatsByLicenseID(); + $available_seats_count = $license->availCount()->count(); + $checkedout_seats_count = ($total_seats_count - $available_seats_count); + + \Log::debug('Total: '.$total_seats_count); + \Log::debug('Users: '.$users_count); + \Log::debug('Available: '.$available_seats_count); + \Log::debug('Checkedout: '.$checkedout_seats_count); + if ($license) { $this->authorize('view', $license); - - return view('licenses/view', compact('license')); + return view('licenses.view', compact('license')) + ->with('users_count', $users_count) + ->with('total_seats_count', $total_seats_count) + ->with('available_seats_count', $available_seats_count) + ->with('checkedout_seats_count', $checkedout_seats_count); } - return redirect()->route('licenses.index') + return redirect()->route('licenses.view') ->with('error', trans('admin/licenses/message.does_not_exist')); } + + /** + * Returns a view with prepopulated data for clone + * + * @author [A. Gianotto] [] + * @param int $licenseId + * @return \Illuminate\Http\RedirectResponse + * @throws \Illuminate\Auth\Access\AuthorizationException + */ public function getClone($licenseId = null) { if (is_null($license_to_clone = License::find($licenseId))) { diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index 67ecae542e..a2d3d496da 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -113,7 +113,8 @@ class BulkUsersController extends Controller ->conditionallyAddItem('locale') ->conditionallyAddItem('remote') ->conditionallyAddItem('ldap_import') - ->conditionallyAddItem('activated'); + ->conditionallyAddItem('activated') + ->conditionallyAddItem('autoassign_licenses'); // If the manager_id is one of the users being updated, generate a warning. diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 7e95484f6e..b839533658 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -120,7 +120,7 @@ class UsersController extends Controller $user->created_by = Auth::user()->id; $user->start_date = $request->input('start_date', null); $user->end_date = $request->input('end_date', null); - $user->autoassign_licenses= $request->input('autoassign_licenses', 1); + $user->autoassign_licenses = $request->input('autoassign_licenses', 0); // Strip out the superuser permission if the user isn't a superadmin $permissions_array = $request->input('permission'); @@ -275,7 +275,7 @@ class UsersController extends Controller $user->website = $request->input('website', null); $user->start_date = $request->input('start_date', null); $user->end_date = $request->input('end_date', null); - $user->autoassign_licenses = $request->input('autoassign_licenses', 1); + $user->autoassign_licenses = $request->input('autoassign_licenses', 0); // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index 3c389a1b18..8df6b89f19 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -56,7 +56,7 @@ class LicensesTransformer 'checkin' => Gate::allows('checkin', License::class), 'clone' => Gate::allows('create', License::class), 'update' => Gate::allows('update', License::class), - 'delete' => Gate::allows('delete', License::class), + 'delete' => (Gate::allows('delete', License::class) && ($license->seats == $license->availCount()->count())) ? true : false, ]; $array += $permissions_array; diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 9447d65455..867a884619 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -56,6 +56,7 @@ class UsersTransformer 'notes'=> e($user->notes), 'permissions' => $user->decodePermissions(), 'activated' => ($user->activated == '1') ? true : false, + 'autoassign_licenses' => ($user->autoassign_licenses == '1') ? true : false, 'ldap_import' => ($user->ldap_import == '1') ? true : false, 'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false, 'two_factor_optin' => ($user->two_factor_active()) ? true : false, diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index bcbc632eb3..9f2c1c5f43 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -58,7 +58,8 @@ class UserImporter extends ItemImporter $this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department')); $this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name')); $this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0; - $this->item['vip'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0; + $this->item['vip'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0; + $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'autoassign_licenses')) ==1 ) ? '1' : 0; $user_department = $this->findCsvMatch($row, 'department'); diff --git a/app/Models/License.php b/app/Models/License.php index ff69d5f66d..162b3d662a 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -106,10 +106,10 @@ class License extends Depreciable * @var array */ protected $searchableRelations = [ - 'manufacturer' => ['name'], - 'company' => ['name'], - 'category' => ['name'], - 'depreciation' => ['name'], + 'manufacturer' => ['name'], + 'company' => ['name'], + 'category' => ['name'], + 'depreciation' => ['name'], ]; /** @@ -425,7 +425,7 @@ class License extends Depreciable public static function assetcount() { return LicenseSeat::whereNull('deleted_at') - ->count(); + ->count(); } @@ -441,8 +441,8 @@ class License extends Depreciable public function totalSeatsByLicenseID() { return LicenseSeat::where('license_id', '=', $this->id) - ->whereNull('deleted_at') - ->count(); + ->whereNull('deleted_at') + ->count(); } /** @@ -486,11 +486,12 @@ class License extends Depreciable public static function availassetcount() { return LicenseSeat::whereNull('assigned_to') - ->whereNull('asset_id') - ->whereNull('deleted_at') - ->count(); + ->whereNull('asset_id') + ->whereNull('deleted_at') + ->count(); } + /** * Returns the number of total available seats for this license * @@ -533,7 +534,7 @@ class License extends Depreciable { return $this->licenseSeatsRelation()->where(function ($query) { $query->whereNotNull('assigned_to') - ->orWhereNotNull('asset_id'); + ->orWhereNotNull('asset_id'); }); } @@ -621,13 +622,13 @@ class License extends Depreciable public function freeSeat() { return $this->licenseseats() - ->whereNull('deleted_at') - ->where(function ($query) { - $query->whereNull('assigned_to') - ->whereNull('asset_id'); - }) - ->orderBy('id', 'asc') - ->first(); + ->whereNull('deleted_at') + ->where(function ($query) { + $query->whereNull('assigned_to') + ->whereNull('asset_id'); + }) + ->orderBy('id', 'asc') + ->first(); } @@ -657,11 +658,11 @@ class License extends Depreciable $days = (is_null($days)) ? 60 : $days; return self::whereNotNull('expiration_date') - ->whereNull('deleted_at') - ->whereRaw(DB::raw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) ')) - ->where('expiration_date', '>', date('Y-m-d')) - ->orderBy('expiration_date', 'ASC') - ->get(); + ->whereNull('deleted_at') + ->whereRaw(DB::raw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) ')) + ->where('expiration_date', '>', date('Y-m-d')) + ->orderBy('expiration_date', 'ASC') + ->get(); } /** @@ -705,4 +706,4 @@ class License extends Depreciable return $query->leftJoin('companies as companies', 'licenses.company_id', '=', 'companies.id')->select('licenses.*') ->orderBy('companies.name', $order); } -} +} \ No newline at end of file diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index d60d3c42fe..fa00fbad2a 100755 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -78,24 +78,7 @@ class Supplier extends SnipeModel { return $this->hasMany(Asset::class)->whereNull('deleted_at')->selectRaw('supplier_id, count(*) as count')->groupBy('supplier_id'); } - - /** - * Sets the license seat count attribute - * - * @todo I don't see the licenseSeatsRelation here? - * - * @author A. Gianotto - * @since [v1.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function getLicenseSeatsCountAttribute() - { - if ($this->licenseSeatsRelation->first()) { - return $this->licenseSeatsRelation->first()->count; - } - - return 0; - } + /** * Establishes the supplier -> assets relationship diff --git a/app/Models/User.php b/app/Models/User.php index efdbb6297c..36e1c8ac47 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -65,6 +65,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'avatar', 'gravatar', 'vip', + 'autoassign_licenses', ]; protected $casts = [ @@ -76,6 +77,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', + 'autoassign_licenses' => 'boolean', ]; /** @@ -95,6 +97,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'location_id' => 'exists:locations,id|nullable', 'start_date' => 'nullable|date_format:Y-m-d', 'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date', + 'autoassign_licenses' => 'boolean', ]; /** diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php index 1e8784f2fc..5a44bf52ab 100644 --- a/app/Presenters/LicensePresenter.php +++ b/app/Presenters/LicensePresenter.php @@ -33,7 +33,7 @@ class LicensePresenter extends Presenter 'field' => 'name', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/licenses/table.title'), + 'title' => trans('general.name'), 'formatter' => 'licensesLinkFormatter', ], [ 'field' => 'product_key', diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index b5eefdf811..fbb41a4d51 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -294,6 +294,15 @@ class UserPresenter extends Presenter 'visible' => true, 'formatter' => 'trueFalseFormatter', ], + [ + 'field' => 'autoassign_licenses', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.autoassign_licenses'), + 'visible' => false, + 'formatter' => 'trueFalseFormatter', + ], [ 'field' => 'created_by', 'searchable' => false, diff --git a/public/css/build/app.css b/public/css/build/app.css index 558970fa05..8ab54a3251 100644 Binary files a/public/css/build/app.css and b/public/css/build/app.css differ diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index 37ec7d15c3..296136c7a6 100644 Binary files a/public/css/build/overrides.css and b/public/css/build/overrides.css differ diff --git a/public/css/dist/all.css b/public/css/dist/all.css index 8585073ccb..e0ffcf96d0 100644 Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css index 5ba6c5eadd..c9f573c75f 100644 Binary files a/public/css/dist/skins/skin-black-dark.css and b/public/css/dist/skins/skin-black-dark.css differ diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css index 5ba6c5eadd..c9f573c75f 100644 Binary files a/public/css/dist/skins/skin-black-dark.min.css and b/public/css/dist/skins/skin-black-dark.min.css differ diff --git a/public/css/dist/skins/skin-blue-dark.css b/public/css/dist/skins/skin-blue-dark.css index a393ee578c..c728a5af28 100644 Binary files a/public/css/dist/skins/skin-blue-dark.css and b/public/css/dist/skins/skin-blue-dark.css differ diff --git a/public/css/dist/skins/skin-blue-dark.min.css b/public/css/dist/skins/skin-blue-dark.min.css index a393ee578c..c728a5af28 100644 Binary files a/public/css/dist/skins/skin-blue-dark.min.css and b/public/css/dist/skins/skin-blue-dark.min.css differ diff --git a/public/css/dist/skins/skin-green-dark.css b/public/css/dist/skins/skin-green-dark.css index 3fe0a606d3..5bf1b300d3 100644 Binary files a/public/css/dist/skins/skin-green-dark.css and b/public/css/dist/skins/skin-green-dark.css differ diff --git a/public/css/dist/skins/skin-green-dark.min.css b/public/css/dist/skins/skin-green-dark.min.css index 3fe0a606d3..5bf1b300d3 100644 Binary files a/public/css/dist/skins/skin-green-dark.min.css and b/public/css/dist/skins/skin-green-dark.min.css differ diff --git a/public/css/dist/skins/skin-orange-dark.css b/public/css/dist/skins/skin-orange-dark.css index fe7af4f628..5f4e719296 100644 Binary files a/public/css/dist/skins/skin-orange-dark.css and b/public/css/dist/skins/skin-orange-dark.css differ diff --git a/public/css/dist/skins/skin-orange-dark.min.css b/public/css/dist/skins/skin-orange-dark.min.css index fe7af4f628..5f4e719296 100644 Binary files a/public/css/dist/skins/skin-orange-dark.min.css and b/public/css/dist/skins/skin-orange-dark.min.css differ diff --git a/public/css/dist/skins/skin-purple-dark.css b/public/css/dist/skins/skin-purple-dark.css index e1bd8edb4a..cec9819d7d 100644 Binary files a/public/css/dist/skins/skin-purple-dark.css and b/public/css/dist/skins/skin-purple-dark.css differ diff --git a/public/css/dist/skins/skin-purple-dark.min.css b/public/css/dist/skins/skin-purple-dark.min.css index e1bd8edb4a..cec9819d7d 100644 Binary files a/public/css/dist/skins/skin-purple-dark.min.css and b/public/css/dist/skins/skin-purple-dark.min.css differ diff --git a/public/css/dist/skins/skin-red-dark.css b/public/css/dist/skins/skin-red-dark.css index cbb5b07d14..bcce285ed1 100644 Binary files a/public/css/dist/skins/skin-red-dark.css and b/public/css/dist/skins/skin-red-dark.css differ diff --git a/public/css/dist/skins/skin-red-dark.min.css b/public/css/dist/skins/skin-red-dark.min.css index cbb5b07d14..bcce285ed1 100644 Binary files a/public/css/dist/skins/skin-red-dark.min.css and b/public/css/dist/skins/skin-red-dark.min.css differ diff --git a/public/css/dist/skins/skin-yellow-dark.css b/public/css/dist/skins/skin-yellow-dark.css index 5691df2194..fddcde6fa7 100644 Binary files a/public/css/dist/skins/skin-yellow-dark.css and b/public/css/dist/skins/skin-yellow-dark.css differ diff --git a/public/css/dist/skins/skin-yellow-dark.min.css b/public/css/dist/skins/skin-yellow-dark.min.css index 5691df2194..fddcde6fa7 100644 Binary files a/public/css/dist/skins/skin-yellow-dark.min.css and b/public/css/dist/skins/skin-yellow-dark.min.css differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 7142f3d899..dd2e0fc1d5 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,24 +1,24 @@ { "/js/build/app.js": "/js/build/app.js?id=7caeae38608edd96421f8ef59d33f5f6", "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374", - "/css/build/overrides.css": "/css/build/overrides.css?id=ce23fa22306439befbf49e8e63adb4e0", - "/css/build/app.css": "/css/build/app.css?id=2e40bdf6f6d3d6d6954c391a0a91285e", + "/css/build/overrides.css": "/css/build/overrides.css?id=ce20eefb1895545e882840c480bca0dc", + "/css/build/app.css": "/css/build/app.css?id=3afc900b0a697567f8285f46aded1c89", "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b", "/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=f343f659ca1d45534d2c2c3cc30fb619", - "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=57e634d63101d3613f4c73aaa2e3f50a", - "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=5120ce6a4b70d11bbc84a5125aa31949", + "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=ca38553d041220a4296dda555940e056", + "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=032f18fdd48936784cfcfe70712a68ae", + "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=e5b6ec4691d8fd647d38722886f983e6", "/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=713b1205aa2d7c9db282f8cd5754c0e4", + "/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=7d92dea45d94be7e1d4e427c728d335d", "/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=6ea1eecb7f939256c373c92f58749e72", - "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=1c0f59079342d1a10099bf41d2e46f59", + "/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=218c6d947f73c767d23a663a9859d97e", + "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=87c6506e9aac3ebc68dfd99b6f983602", "/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=c0d21166315b7c2cdd4819fa4a5e4d1e", + "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=28b36223cf7b1d6e5f236859a4ef2b45", "/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/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/all.css": "/css/dist/all.css?id=1faccd9b34013f9893ed467fa3ddcb39", + "/css/dist/all.css": "/css/dist/all.css?id=2b87a5b5f1e6f09861732fa41d159bc4", "/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", "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=2df05d4beaa48550d71234e8dca79141", @@ -34,18 +34,18 @@ "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=7a506bf59323cf5b5fe97f7080fc5ee0", "/js/dist/all.js": "/js/dist/all.js?id=97b1034b75e3ac29a2eb9770d66c3370", "/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=c0d21166315b7c2cdd4819fa4a5e4d1e", + "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=28b36223cf7b1d6e5f236859a4ef2b45", "/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=1c0f59079342d1a10099bf41d2e46f59", + "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=87c6506e9aac3ebc68dfd99b6f983602", "/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=57e634d63101d3613f4c73aaa2e3f50a", + "/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=032f18fdd48936784cfcfe70712a68ae", "/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=5120ce6a4b70d11bbc84a5125aa31949", + "/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=e5b6ec4691d8fd647d38722886f983e6", "/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=6ea1eecb7f939256c373c92f58749e72", + "/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=218c6d947f73c767d23a663a9859d97e", "/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=713b1205aa2d7c9db282f8cd5754c0e4", + "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=7d92dea45d94be7e1d4e427c728d335d", "/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=f343f659ca1d45534d2c2c3cc30fb619", + "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=ca38553d041220a4296dda555940e056", "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da" } diff --git a/resources/assets/less/overrides.less b/resources/assets/less/overrides.less index 23d87fe6f4..60a75358fb 100644 --- a/resources/assets/less/overrides.less +++ b/resources/assets/less/overrides.less @@ -141,6 +141,9 @@ a.accordion-header { .dropdown-menu li a { //color: inherit; } +.pull-text-right{ + text-align: right !important; +} .main-header .sidebar-toggle:before { content: "\f0c9"; diff --git a/resources/lang/en/admin/hardware/general.php b/resources/lang/en/admin/hardware/general.php index 3d1e43c2df..b0a48f2ce4 100644 --- a/resources/lang/en/admin/hardware/general.php +++ b/resources/lang/en/admin/hardware/general.php @@ -23,6 +23,7 @@ return [ 'restore' => 'Restore Asset', 'pending' => 'Pending', 'undeployable' => 'Undeployable', + 'undeployable_tooltip' => 'This asset has a status label that is undeployable and cannot be checked out at this time.', 'view' => 'View Asset', 'csv_error' => 'You have an error in your CSV file:', 'import_text' => ' diff --git a/resources/lang/en/admin/licenses/general.php b/resources/lang/en/admin/licenses/general.php index 25a536ec56..0187d076a3 100644 --- a/resources/lang/en/admin/licenses/general.php +++ b/resources/lang/en/admin/licenses/general.php @@ -1,8 +1,8 @@ 'About Licenses', - 'about_licenses' => 'Licenses are used to track software. They have a specified number of seats that can be checked out to individuals', + 'about_licenses_title' => 'About Licenses', + 'about_licenses' => 'Licenses are used to track software. They have a specified number of seats that can be checked out to individuals', 'checkin' => 'Checkin License Seat', 'checkout_history' => 'Checkout History', 'checkout' => 'Checkout License Seat', @@ -18,4 +18,30 @@ return array( 'software_licenses' => 'Software Licenses', 'user' => 'User', 'view' => 'View License', + 'delete_disabled' => 'This license cannot be deleted yet because some seats are still checked out.', + 'bulk' => + [ + 'checkin_all' => [ + 'button' => 'Checkin All Seats', + 'modal' => 'This will action checkin one seat. | This action will checkin all :checkedout_seats_count seats for this license.', + 'enabled_tooltip' => 'Checkin ALL seats for this license from both users and assets', + 'disabled_tooltip' => 'This is disabled because there are no seats currently checked out', + 'success' => 'License successfully checked in! | All licenses were successfully checked in!', + 'log_msg' => 'Checked in via bulk license checkout in license GUI', + ], + + 'checkout_all' => [ + 'button' => 'Checkout All Seats', + 'modal' => 'This action will checkout one seat to the first available user. | This action will checkout all :available_seats_count seats to the first available users. A user is considered available for this seat if they do not already have this license checked out to them, and the Auto-Assign License property is enabled on their user account.', + 'enabled_tooltip' => 'Checkout ALL seats (or as many as are available) to ALL users', + 'disabled_tooltip' => 'This is disabled because there are no seats currently available', + 'success' => 'License successfully checked out! | :count licenses were successfully checked out!', + 'error_no_seats' => 'There are no remaining seats left for this license.', + 'warn_not_enough_seats' => ':count users were assigned this license, but we ran out of available license seats.', + 'warn_no_avail_users' => 'Nothing to do. There are no users who do not already have this license assigned to them.', + 'log_msg' => 'Checked out via bulk license checkout in license GUI', + + + ], + ], ); diff --git a/resources/lang/en/general.php b/resources/lang/en/general.php index cb51b28a42..328eb32090 100644 --- a/resources/lang/en/general.php +++ b/resources/lang/en/general.php @@ -439,4 +439,13 @@ return [ 'setup_migration_output' => 'Migration output:', 'setup_migration_create_user' => 'Next: Create User', 'importer_generic_error' => 'Your file import is complete, but we did receive an error. This is usually caused by third-party API throttling from a notification webhook (such as Slack) and would not have interfered with the import itself, but you should confirm this.', + 'confirm' => 'Confirm', + 'autoassign_licenses' => 'Auto-Assign Licenses', + 'autoassign_licenses_help' => 'Allow user to be have licenses assigned via the bulk-assign license UI or cli tools.', + 'autoassign_licenses_help_long' => 'This allows a user to be have licenses assigned via the bulk-assign license UI or cli tools. (For example, you might not want contractors to be auto-assigned a license you would provide to only staff members. You can still individually assign licenses to those users, but they will not be included in the Checkout License to All Users functions.)', + 'no_autoassign_licenses_help' => 'Do not include user for bulk-assigning through the license UI or cli tools.', + 'modal_confirm_generic' => 'Are you sure?', + 'cannot_be_deleted' => 'This item cannot be deleted', + 'undeployable_tooltip' => 'This item cannot be checked out. Check the quantity remaining.', + ]; diff --git a/resources/views/custom_fields/index.blade.php b/resources/views/custom_fields/index.blade.php index d2435d7db5..0ad8511183 100644 --- a/resources/views/custom_fields/index.blade.php +++ b/resources/views/custom_fields/index.blade.php @@ -21,7 +21,7 @@

{{ trans('admin/custom_fields/general.fieldsets') }}

@@ -111,7 +111,7 @@

{{ trans('admin/custom_fields/general.custom_fields') }}

diff --git a/resources/views/groups/edit.blade.php b/resources/views/groups/edit.blade.php index f5948e4841..cb95b3762a 100755 --- a/resources/views/groups/edit.blade.php +++ b/resources/views/groups/edit.blade.php @@ -77,7 +77,7 @@ @unless (empty($localPermission['label'])) @@ -100,7 +100,7 @@

{{ $area }}

@@ -122,7 +122,7 @@ {{ $this_permission['label'] }} diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index e33f681f6e..ccd3f86536 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -456,7 +456,7 @@
@if ($field->field_encrypted=='1') - + @endif @if ($field->isFieldDecryptable($asset->{$field->db_column_name()} )) diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index 37554e6a83..9eb43d35ae 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -850,7 +850,6 @@
-
+ @@ -974,7 +974,7 @@ $(function () { - $('[data-toggle="tooltip"]').tooltip(); + $('[data-tooltip="true"]').tooltip(); $('[data-toggle="popover"]').popover(); $('.select2 span').addClass('needsclick'); $('.select2 span').removeAttr('title'); diff --git a/resources/views/licenses/view.blade.php b/resources/views/licenses/view.blade.php index 21260ae4bb..e48ab859bd 100755 --- a/resources/views/licenses/view.blade.php +++ b/resources/views/licenses/view.blade.php @@ -10,7 +10,7 @@ {{-- Page content --}} @section('content')
-
+
- {!! $license->maintained ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! $license->maintained ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
@@ -358,7 +344,7 @@
- {!! $license->reassignable ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! $license->reassignable ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
@@ -378,8 +364,10 @@ - - + + + + @@ -550,13 +538,100 @@ + + + + +
+ + @can('update', $license) + {{ trans('admin/licenses/general.edit') }} + {{ trans('admin/licenses/general.clone') }} + @endcan + + @can('checkout', $license) + + @if ($license->availCount()->count() > 0) + + {{ trans('general.checkout') }} + + + {{ trans('admin/licenses/general.bulk.checkout_all.button') }} + + + @else + + {{ trans('general.checkout') }} + + + + {{ trans('admin/licenses/general.bulk.checkout_all.button') }} + + + @endif + @endcan + + @can('checkin', $license) + + @if (($license->seats - $license->availCount()->count()) > 0 ) + + {{ trans('admin/licenses/general.bulk.checkin_all.button') }} + + @else + + + {{ trans('admin/licenses/general.bulk.checkin_all.button') }} + + + @endif + @endcan + + @can('delete', $license) + + @if ($license->availCount()->count() == $license->seats) + + @else + + + {{ trans('general.delete') }} + + + @endif + @endcan +
+ + +@can('checkin', \App\Models\License::class) + @include ('modals.confirm-action', + [ + 'modal_name' => 'checkinFromAllModal', + 'route' => route('licenses.bulkcheckin', $license->id), + 'title' => trans('general.modal_confirm_generic'), + 'body' => trans_choice('admin/licenses/general.bulk.checkin_all.modal', 2, ['checkedout_seats_count' => $checkedout_seats_count]) + ]) +@endcan + +@can('checkout', \App\Models\License::class) + @include ('modals.confirm-action', + [ + 'modal_name' => 'checkoutFromAllModal', + 'route' => route('licenses.bulkcheckout', $license->id), + 'title' => trans('general.modal_confirm_generic'), + 'body' => trans_choice('admin/licenses/general.bulk.checkout_all.modal', 2, ['available_seats_count' => $available_seats_count]) + ]) +@endcan + + + @can('update', \App\Models\License::class) @include ('modals.upload-file', ['item_type' => 'license', 'item_id' => $license->id]) @endcan @@ -565,5 +640,15 @@ @section('moar_scripts') + @include ('partials.bootstrap-table') @stop diff --git a/resources/views/modals/confirm-action.blade.php b/resources/views/modals/confirm-action.blade.php new file mode 100644 index 0000000000..135a9799ed --- /dev/null +++ b/resources/views/modals/confirm-action.blade.php @@ -0,0 +1,27 @@ + + diff --git a/resources/views/modals/upload-file.blade.php b/resources/views/modals/upload-file.blade.php index f87e3e4962..09c53751ce 100644 --- a/resources/views/modals/upload-file.blade.php +++ b/resources/views/modals/upload-file.blade.php @@ -4,7 +4,7 @@
- + {{ trans('general.min_amt_help') }} diff --git a/resources/views/partials/forms/edit/permissions-base.blade.php b/resources/views/partials/forms/edit/permissions-base.blade.php index a702c619ae..a6a6abf9e0 100644 --- a/resources/views/partials/forms/edit/permissions-base.blade.php +++ b/resources/views/partials/forms/edit/permissions-base.blade.php @@ -4,7 +4,7 @@ @@ -76,7 +76,7 @@ @if ($permission['display']) diff --git a/resources/views/users/bulk-edit.blade.php b/resources/views/users/bulk-edit.blade.php index 10ae6261d0..bdc1ad9289 100644 --- a/resources/views/users/bulk-edit.blade.php +++ b/resources/views/users/bulk-edit.blade.php @@ -119,6 +119,29 @@
+ +
+
+ {{ trans('general.autoassign_licenses') }} +
+
+ + + + + +
+
+
diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index a94f0734b7..34005e9bc7 100755 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -370,6 +370,20 @@
+ + +
+
+ + + +

{{ trans('general.autoassign_licenses_help_long') }}

+
+
+
@@ -383,15 +397,6 @@
- -
-
- -
-
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id']) @@ -444,8 +449,8 @@
-
- {!! Form::countries('country', old('country', $user->country), 'col-md-6 select2') !!} +
+ {!! Form::countries('country', old('country', $user->country), 'col-md-12 select2') !!} {!! $errors->first('country', '') !!}
diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index a130d1629d..47d6dea328 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -122,7 +122,8 @@ @endcan @can('update', \App\Models\User::class) -
  • +
  • + @@ -519,23 +520,23 @@
  • @endif - +
    {{ trans('admin/users/general.vip_label') }}
    - {!! ($user->vip=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! ($user->vip=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
    - +
    {{ trans('admin/users/general.remote') }}
    - {!! ($user->remote=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! ($user->remote=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
    @@ -545,17 +546,28 @@ {{ trans('general.login_enabled') }}
    - {!! ($user->activated=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! ($user->activated=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
    - + +
    +
    + {{ trans('general.autoassign_licenses') }} +
    +
    + {!! ($user->autoassign_licenses=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!} +
    +
    + + +
    LDAP
    - {!! ($user->ldap_import=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! ($user->ldap_import=='1') ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
    @@ -569,7 +581,7 @@
    - {!! ($user->two_factor_active()) ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! ($user->two_factor_active()) ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
    @@ -580,7 +592,7 @@ {{ trans('admin/users/general.two_factor_enrolled') }}
    - {!! ($user->two_factor_active_and_enrolled()) ? ' '.trans('general.yes') : ' '.trans('general.no') !!} + {!! ($user->two_factor_active_and_enrolled()) ? ' '.trans('general.yes') : ' '.trans('general.no') !!}
    @@ -590,11 +602,11 @@
    - +
    -
    +
    - + {{ trans('admin/settings/general.two_factor_reset') }} @@ -1031,9 +1043,9 @@ $(function () { dataType: 'json', success: function (data) { - $("#two_factor_reset_toggle").html('').html(' {{ trans('general.no') }}'); + $("#two_factor_reset_toggle").html('').html(' {{ trans('general.no') }}'); $("#two_factor_reseticon").html(''); - $("#two_factor_resetstatus").html('' + data.message); + $("#two_factor_resetstatus").html(' ' + data.message + ''); }, diff --git a/routes/web/licenses.php b/routes/web/licenses.php index ebcb5a2b2b..b70347793c 100644 --- a/routes/web/licenses.php +++ b/routes/web/licenses.php @@ -25,10 +25,21 @@ Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () { [Licenses\LicenseCheckinController::class, 'store'] )->name('licenses.checkin.save'); + Route::post( + '{licenseId}/bulkcheckin', + [Licenses\LicenseCheckinController::class, 'bulkCheckin'] + )->name('licenses.bulkcheckin'); + + Route::post( + '{licenseId}/bulkcheckout', + [Licenses\LicenseCheckoutController::class, 'bulkCheckout'] + )->name('licenses.bulkcheckout'); + Route::post( '{licenseId}/upload', [Licenses\LicenseFilesController::class, 'store'] )->name('upload/license'); + Route::delete( '{licenseId}/deletefile/{fileId}', [Licenses\LicenseFilesController::class, 'destroy']