diff --git a/.env.docker b/.env.docker index 9e50383014..4865f8e13a 100644 --- a/.env.docker +++ b/.env.docker @@ -97,7 +97,7 @@ API_TOKEN_EXPIRATION_YEARS=40 # -------------------------------------------- # OPTIONAL: SECURITY HEADER SETTINGS # -------------------------------------------- -APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.0.0.0/8 +APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.16.0.0/12 ALLOW_IFRAMING=false REFERRER_POLICY=same-origin ENABLE_CSP=false diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 14ddea2258..5a4042aee4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,7 +14,8 @@ jobs: - uses: actions/stale@v9 with: debug-only: true - operations-per-run: 100 # just while we're debugging + ascending: true + operations-per-run: 1000 # just while we're debugging repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 60 days-before-close: 7 diff --git a/README.md b/README.md index 0086c7b327..e0f4154f6c 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,11 @@ Since the release of the JSON REST API, several third-party developers have been ### Contributing -Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview). +Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them. + +Ideally, contributions should follow from a human-to-human discussion in the form of an issue. + +Please see the complete documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview). Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php old mode 100755 new mode 100644 index 845db27ef9..9f4281bd46 --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -53,18 +53,22 @@ class LdapSync extends Command ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); - $ldap_result_username = Setting::getSettings()->ldap_username_field; - $ldap_result_last_name = Setting::getSettings()->ldap_lname_field; - $ldap_result_first_name = Setting::getSettings()->ldap_fname_field; - $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag; - $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num; - $ldap_result_email = Setting::getSettings()->ldap_email; - $ldap_result_phone = Setting::getSettings()->ldap_phone_field; - $ldap_result_jobtitle = Setting::getSettings()->ldap_jobtitle; - $ldap_result_country = Setting::getSettings()->ldap_country; - $ldap_result_location = Setting::getSettings()->ldap_location; - $ldap_result_dept = Setting::getSettings()->ldap_dept; - $ldap_result_manager = Setting::getSettings()->ldap_manager; + + $ldap_map = [ + "username" => Setting::getSettings()->ldap_username_field, + "last_name" => Setting::getSettings()->ldap_lname_field, + "first_name" => Setting::getSettings()->ldap_fname_field, + "active_flag" => Setting::getSettings()->ldap_active_flag, + "emp_num" => Setting::getSettings()->ldap_emp_num, + "email" => Setting::getSettings()->ldap_email, + "phone" => Setting::getSettings()->ldap_phone_field, + "jobtitle" => Setting::getSettings()->ldap_jobtitle, + "country" => Setting::getSettings()->ldap_country, + "location" => Setting::getSettings()->ldap_location, + "dept" => Setting::getSettings()->ldap_dept, + "manager" => Setting::getSettings()->ldap_manager, + ]; + $ldap_default_group = Setting::getSettings()->ldap_default_group; $search_base = Setting::getSettings()->ldap_base_dn; @@ -107,14 +111,21 @@ class LdapSync extends Command } /** - * If a filter has been specified, use that + * If a filter has been specified, use that, otherwise default to null */ if ($this->option('filter') != '') { - $results = Ldap::findLdapUsers($search_base, -1, $this->option('filter')); + $filter = $this->option('filter'); } else { - $results = Ldap::findLdapUsers($search_base); + $filter = null; } - + + /** + * We only need to request the LDAP attributes that we process + */ + $attributes = array_values(array_filter($ldap_map)); + + $results = Ldap::findLdapUsers($search_base, -1, $filter, $attributes); + } catch (\Exception $e) { if ($this->option('json_summary')) { $json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []]; @@ -126,23 +137,24 @@ class LdapSync extends Command } /* Determine which location to assign users to by default. */ - $location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose + $default_location = null; if ($this->option('location') != '') { - if ($location = Location::where('name', '=', $this->option('location'))->first()) { + if ($default_location = Location::where('name', '=', $this->option('location'))->first()) { Log::debug('Location name ' . $this->option('location') . ' passed'); - Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')'); + Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')'); } } elseif ($this->option('location_id')) { + //TODO - figure out how or why this is an array? foreach($this->option('location_id') as $location_id) { - if ($location = Location::where('id', '=', $location_id)->first()) { + if ($default_location = Location::where('id', '=', $location_id)->first()) { Log::debug('Location ID ' . $location_id . ' passed'); - Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')'); + Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')'); } } } - if (! isset($location)) { + if (!isset($default_location)) { Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.'); } @@ -183,17 +195,17 @@ class LdapSync extends Command } $usernames = []; for ($i = 0; $i < $location_users['count']; $i++) { - if (array_key_exists($ldap_result_username, $location_users[$i])) { + if (array_key_exists($ldap_map["username"], $location_users[$i])) { $location_users[$i]['ldap_location_override'] = true; $location_users[$i]['location_id'] = $ldap_loc['id']; - $usernames[] = $location_users[$i][$ldap_result_username][0]; + $usernames[] = $location_users[$i][$ldap_map["username"]][0]; } } // Delete located users from the general group. foreach ($results as $key => $generic_entry) { - if ((is_array($generic_entry)) && (array_key_exists($ldap_result_username, $generic_entry))) { - if (in_array($generic_entry[$ldap_result_username][0], $usernames)) { + if ((is_array($generic_entry)) && (array_key_exists($ldap_map["username"], $generic_entry))) { + if (in_array($generic_entry[$ldap_map["username"]][0], $usernames)) { unset($results[$key]); } } @@ -218,77 +230,78 @@ class LdapSync extends Command for ($i = 0; $i < $results['count']; $i++) { - $item = []; - $item['username'] = $results[$i][$ldap_result_username][0] ?? ''; - $item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? ''; - $item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? ''; - $item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? ''; - $item['email'] = $results[$i][$ldap_result_email][0] ?? ''; - $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? ''; - $item['location_id'] = $results[$i]['location_id'] ?? ''; - $item['telephone'] = $results[$i][$ldap_result_phone][0] ?? ''; - $item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? ''; - $item['country'] = $results[$i][$ldap_result_country][0] ?? ''; - $item['department'] = $results[$i][$ldap_result_dept][0] ?? ''; - $item['manager'] = $results[$i][$ldap_result_manager][0] ?? ''; - $item['location'] = $results[$i][$ldap_result_location][0] ?? ''; + $item = []; + $item['username'] = $results[$i][$ldap_map["username"]][0] ?? ''; + $item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? ''; + $item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? ''; + $item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? ''; + $item['email'] = $results[$i][$ldap_map["email"]][0] ?? ''; + $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? ''; + $item['location_id'] = $results[$i]['location_id'] ?? ''; + $item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? ''; + $item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? ''; + $item['country'] = $results[$i][$ldap_map["country"]][0] ?? ''; + $item['department'] = $results[$i][$ldap_map["dept"]][0] ?? ''; + $item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? ''; + $item['location'] = $results[$i][$ldap_map["location"]][0] ?? ''; + $location = $default_location; //initially, set '$location' to the default_location (which may just be `null`) - // ONLY if you are using the "ldap_location" option *AND* you have an actual result - if ($ldap_result_location && $item['location']) { - $location = Location::firstOrCreate([ - 'name' => $item['location'], - ]); - } - $department = Department::firstOrCreate([ - 'name' => $item['department'], + // ONLY if you are using the "ldap_location" option *AND* you have an actual result + if ($ldap_map["location"] && $item['location']) { + $location = Location::firstOrCreate([ + 'name' => $item['location'], ]); + } + $department = Department::firstOrCreate([ + 'name' => $item['department'], + ]); - $user = User::where('username', $item['username'])->first(); - if ($user) { - // Updating an existing user. - $item['createorupdate'] = 'updated'; - } else { - // Creating a new user. - $user = new User; - $user->password = $user->noPassword(); - $user->locale = app()->getLocale(); - $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below) - $item['createorupdate'] = 'created'; - } + $user = User::where('username', $item['username'])->first(); + if ($user) { + // Updating an existing user. + $item['createorupdate'] = 'updated'; + } else { + // Creating a new user. + $user = new User; + $user->password = $user->noPassword(); + $user->locale = app()->getLocale(); + $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below) + $item['createorupdate'] = 'created'; + } //If a sync option is not filled in on the LDAP settings don't populate the user field - if($ldap_result_username != null){ + if($ldap_map["username"] != null){ $user->username = $item['username']; } - if($ldap_result_last_name != null){ + if($ldap_map["last_name"] != null){ $user->last_name = $item['lastname']; } - if($ldap_result_first_name != null){ + if($ldap_map["first_name"] != null){ $user->first_name = $item['firstname']; } - if($ldap_result_emp_num != null){ + if($ldap_map["emp_num"] != null){ $user->employee_num = e($item['employee_number']); } - if($ldap_result_email != null){ + if($ldap_map["email"] != null){ $user->email = $item['email']; } - if($ldap_result_phone != null){ + if($ldap_map["phone"] != null){ $user->phone = $item['telephone']; } - if($ldap_result_jobtitle != null){ + if($ldap_map["jobtitle"] != null){ $user->jobtitle = $item['jobtitle']; } - if($ldap_result_country != null){ + if($ldap_map["country"] != null){ $user->country = $item['country']; } - if($ldap_result_dept != null){ + if($ldap_map["dept"] != null){ $user->department_id = $department->id; } - if($ldap_result_location != null){ - $user->location_id = $location ? $location->id : null; + if($ldap_map["location"] != null){ + $user->location_id = $location?->id; } - if($ldap_result_manager != null){ + if($ldap_map["manager"] != null){ if($item['manager'] != null) { // Check Cache first if (isset($manager_cache[$item['manager']])) { @@ -305,7 +318,7 @@ class LdapSync extends Command $ldap_manager = [ "count" => 1, 0 => [ - $ldap_result_username => [$item['manager']] + $ldap_map["username"] => [$item['manager']] ] ]; } @@ -314,7 +327,7 @@ class LdapSync extends Command // Get the Manager's username // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array. - $ldapManagerUsername = $ldap_manager[0][$ldap_result_username][0]; + $ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0]; // Get User from Manager username. $ldap_manager = User::where('username', $ldapManagerUsername)->first(); @@ -330,38 +343,38 @@ class LdapSync extends Command } } - // Sync activated state for Active Directory. - if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set.... - // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them. - // (Specifically, we don't handle a value of '0.0' correctly) - $raw_value = @$results[$i][$ldap_result_active_flag][0]; - $filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); - $boolean_cast = (bool)$raw_value; + // Sync activated state for Active Directory. + if (!empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set.... + // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them. + // (Specifically, we don't handle a value of '0.0' correctly) + $raw_value = @$results[$i][$ldap_map["active_flag"]][0]; + $filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + $boolean_cast = (bool) $raw_value; - $user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast + $user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast - } elseif (array_key_exists('useraccountcontrol', $results[$i]) ) { - // ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists, - // ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in + } elseif (array_key_exists('useraccountcontrol', $results[$i])) { + // ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists, + // ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in - /* The following is _probably_ the correct logic, but we can't use it because - some users may have been dependent upon the previous behavior, and this - could cause additional access to be available to users they don't want - to allow to log in. + /* The following is _probably_ the correct logic, but we can't use it because + some users may have been dependent upon the previous behavior, and this + could cause additional access to be available to users they don't want + to allow to log in. - $useraccountcontrol = $results[$i]['useraccountcontrol'][0]; - if( - // based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties - ($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT - !($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE - !($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT - ) { - $user->activated = 1; - } else { - $user->activated = 0; - } */ - $enabled_accounts = [ + $useraccountcontrol = $results[$i]['useraccountcontrol'][0]; + if( + // based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties + ($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT + !($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE + !($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT + ) { + $user->activated = 1; + } else { + $user->activated = 0; + } */ + $enabled_accounts = [ '512', // 0x200 NORMAL_ACCOUNT '544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD '66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD @@ -374,44 +387,47 @@ class LdapSync extends Command '4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH '1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED '1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED, - ]; - $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0; + ]; + $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0; // If we're not using AD, and there isn't an activated flag set, activate all users - } /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active. - already-existing accounts will be however the administrator has set them */ + } /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active. + already-existing accounts will be however the administrator has set them */ - if ($item['ldap_location_override'] == true) { - $user->location_id = $item['location_id']; - } elseif ((isset($location)) && (! empty($location))) { - if ((is_array($location)) && (array_key_exists('id', $location))) { - $user->location_id = $location['id']; - } elseif (is_object($location)) { - $user->location_id = $location->id; - } + if ($item['ldap_location_override'] == true) { + $user->location_id = $item['location_id']; + } elseif ((isset($location)) && (!empty($location))) { + if ((is_array($location)) && (array_key_exists('id', $location))) { + $user->location_id = $location['id']; + } elseif (is_object($location)) { + $user->location_id = $location->id; //THIS is the magic line, this should do it. } - $location = null; - $user->ldap_import = 1; + } + // TODO - should we be NULLING locations if $location is really `null`, and that's what we came up with? + // will that conflict with any overriding setting that the user set? Like, if they moved someone from + // the 'null' location to somewhere, we wouldn't want to try to override that, right? + $location = null; + $user->ldap_import = 1; - $errors = ''; + $errors = ''; - if ($user->save()) { - $item['note'] = $item['createorupdate']; - $item['status'] = 'success'; - if ( $item['createorupdate'] === 'created' && $ldap_default_group) { - $user->groups()->attach($ldap_default_group); - } - - } else { - foreach ($user->getErrors()->getMessages() as $key => $err) { - $errors .= $err[0]; - } - $item['note'] = $errors; - $item['status'] = 'error'; + if ($user->save()) { + $item['note'] = $item['createorupdate']; + $item['status'] = 'success'; + if ($item['createorupdate'] === 'created' && $ldap_default_group) { + $user->groups()->attach($ldap_default_group); } - array_push($summary, $item); + } else { + foreach ($user->getErrors()->getMessages() as $key => $err) { + $errors .= $err[0]; + } + $item['note'] = $errors; + $item['status'] = 'error'; + } + + array_push($summary, $item); } if ($this->option('summary')) { diff --git a/app/Console/Commands/SendAcceptanceReminder.php b/app/Console/Commands/SendAcceptanceReminder.php index a11ea8e270..1551348046 100644 --- a/app/Console/Commands/SendAcceptanceReminder.php +++ b/app/Console/Commands/SendAcceptanceReminder.php @@ -50,7 +50,7 @@ class SendAcceptanceReminder extends Command $query->where('accepted_at', null) ->where('declined_at', null); }) - ->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.admin']) + ->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser']) ->get(); $count = 0; diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index 4fd5a4c547..8c66c9a3b2 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -73,7 +73,7 @@ class AccessoriesController extends Controller $accessory->purchase_date = request('purchase_date'); $accessory->purchase_cost = request('purchase_cost'); $accessory->qty = request('qty'); - $accessory->user_id = auth()->id(); + $accessory->created_by = auth()->id(); $accessory->supplier_id = request('supplier_id'); $accessory->notes = request('notes'); diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php index 03fb6ac250..2417f16567 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php @@ -78,7 +78,7 @@ class AccessoryCheckoutController extends Controller AccessoryCheckout::create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), - 'user_id' => Auth::id(), + 'created_by' => auth()->id(), 'assigned_to' => $target->id, 'assigned_type' => $target::class, 'note' => $request->input('note'), diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index 6d84861fb0..e29fa7c63b 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -237,7 +237,11 @@ class AcceptanceController extends Controller } $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note')); - $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); + try { + $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); + } catch (\Exception $e) { + Log::warning($e); + } event(new CheckoutAccepted($acceptance)); $return_msg = trans('admin/users/message.accepted'); diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index b1506e4f40..d1ef72bcca 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -56,8 +56,9 @@ class AccessoriesController extends Controller ]; - $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier') - ->withCount('checkouts as checkouts_count'); + $accessories = Accessory::select('accessories.*') + ->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser') + ->withCount('checkouts as checkouts_count'); if ($request->filled('search')) { $accessories = $accessories->TextSearch($request->input('search')); @@ -110,7 +111,10 @@ class AccessoriesController extends Controller break; case 'supplier': $accessories = $accessories->OrderSupplier($order); - break; + break; + case 'created_by': + $accessories = $accessories->OrderByCreatedByName($order); + break; default: $accessories = $accessories->orderBy($column_sort, $order); break; @@ -133,7 +137,6 @@ class AccessoriesController extends Controller */ public function store(StoreAccessoryRequest $request) { - $this->authorize('create', Accessory::class); $accessory = new Accessory; $accessory->fill($request->all()); $accessory = $request->handleImages($accessory); @@ -193,9 +196,6 @@ class AccessoriesController extends Controller $this->authorize('view', Accessory::class); $accessory = Accessory::with('lastCheckout')->findOrFail($id); - if (! Company::isCurrentUserHasAccess($accessory)) { - return ['total' => 0, 'rows' => []]; - } $offset = request('offset', 0); $limit = request('limit', 50); @@ -287,7 +287,7 @@ class AccessoriesController extends Controller AccessoryCheckout::create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), - 'user_id' => Auth::id(), + 'created_by' => auth()->id(), 'assigned_to' => $target->id, 'assigned_type' => $target::class, 'note' => $request->input('note'), @@ -321,7 +321,7 @@ class AccessoriesController extends Controller $accessory = Accessory::find($accessory_checkout->accessory_id); $this->authorize('checkin', $accessory); - $logaction = $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note')); + $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note')); // Was the accessory updated? if ($accessory_checkout->delete()) { @@ -329,14 +329,6 @@ class AccessoriesController extends Controller $user = User::find($accessory_checkout->assigned_to); } - $data['log_id'] = $logaction->id; - $data['first_name'] = $user->first_name; - $data['last_name'] = $user->last_name; - $data['item_name'] = $accessory->name; - $data['checkin_date'] = $logaction->created_at; - $data['item_tag'] = ''; - $data['note'] = $logaction->note; - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success'))); } diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php index ac247a8873..3e02a56195 100644 --- a/app/Http/Controllers/Api/AssetMaintenancesController.php +++ b/app/Http/Controllers/Api/AssetMaintenancesController.php @@ -34,7 +34,7 @@ class AssetMaintenancesController extends Controller $this->authorize('view', Asset::class); $maintenances = AssetMaintenance::select('asset_maintenances.*') - ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin'); + ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser'); if ($request->filled('search')) { $maintenances = $maintenances->TextSearch($request->input('search')); @@ -48,6 +48,10 @@ class AssetMaintenancesController extends Controller $maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id')); } + if ($request->filled('created_by')) { + $maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by')); + } + if ($request->filled('asset_maintenance_type')) { $maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type')); } @@ -69,7 +73,7 @@ class AssetMaintenancesController extends Controller 'asset_tag', 'asset_name', 'serial', - 'user_id', + 'created_by', 'supplier', 'is_warranty', 'status_label', @@ -79,8 +83,8 @@ class AssetMaintenancesController extends Controller $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; switch ($sort) { - case 'user_id': - $maintenances = $maintenances->OrderAdmin($order); + case 'created_by': + $maintenances = $maintenances->OrderByCreatedBy($order); break; case 'supplier': $maintenances = $maintenances->OrderBySupplier($order); @@ -124,7 +128,7 @@ class AssetMaintenancesController extends Controller // create a new model instance $maintenance = new AssetMaintenance(); $maintenance->fill($request->all()); - $maintenance->user_id = Auth::id(); + $maintenance->created_by = auth()->id(); // Was the asset maintenance created? if ($maintenance->save()) { @@ -186,11 +190,8 @@ class AssetMaintenancesController extends Controller { $this->authorize('update', Asset::class); // Check if the asset maintenance exists - $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); - if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot delete a maintenance for that asset')); - } + $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); $assetMaintenance->delete(); diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index 9f78193420..e1ae0c12d3 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -48,6 +48,8 @@ class AssetModelsController extends Controller 'assets_count', 'category', 'fieldset', + 'deleted_at', + 'updated_at', ]; $assetmodels = AssetModel::select([ @@ -67,7 +69,7 @@ class AssetModelsController extends Controller 'models.deleted_at', 'models.updated_at', ]) - ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues') + ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues','adminuser') ->withCount('assets as assets_count'); if ($request->input('status')=='deleted') { diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index 1243f1212a..d4a103be37 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -56,6 +56,11 @@ class AssetsController extends Controller public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array { + + // This handles the legacy audit endpoints :( + if ($action == 'audit') { + $action = 'audits'; + } $filter_non_deprecable_assets = false; /** @@ -121,7 +126,7 @@ class AssetsController extends Controller } $assets = Asset::select('assets.*') - ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', + ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', 'adminuser','model.depreciation', 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. @@ -154,8 +159,8 @@ class AssetsController extends Controller * Handle due and overdue audits and checkin dates */ switch ($action) { - case 'audits': - + // Audit (singular) is left over from earlier legacy APIs + case 'audits' : switch ($upcoming_status) { case 'due': $assets->DueForAudit($settings); @@ -371,8 +376,33 @@ class AssetsController extends Controller case 'assigned_to': $assets->OrderAssigned($order); break; + case 'created_by': + $assets->OrderByCreatedByName($order); + break; default: - $assets->orderBy($column_sort, $order); + $numeric_sort = false; + + // Search through the custom fields array to see if we're sorting on a custom field + if (array_search($column_sort, $all_custom_fields->pluck('db_column')->toArray()) !== false) { + + // Check to see if this is a numeric field type + foreach ($all_custom_fields as $field) { + if (($field->db_column == $sort_override) && ($field->format == 'NUMERIC')) { + $numeric_sort = true; + break; + } + } + + // This may not work for all databases, but it works for MySQL + if ($numeric_sort) { + $assets->orderByRaw(DB::getTablePrefix() . 'assets.' . $sort_override . ' * 1 ' . $order); + } else { + $assets->orderBy($sort_override, $order); + } + + } else { + $assets->orderBy($column_sort, $order); + } break; } @@ -568,7 +598,7 @@ class AssetsController extends Controller $asset->model()->associate(AssetModel::find((int) $request->get('model_id'))); $asset->fill($request->validated()); - $asset->user_id = Auth::id(); + $asset->created_by = auth()->id(); /** * this is here just legacy reasons. Api\AssetController @@ -750,9 +780,16 @@ class AssetsController extends Controller if ($asset = Asset::find($id)) { $this->authorize('delete', $asset); - DB::table('assets') - ->where('id', $asset->id) - ->update(['assigned_to' => null]); + if ($asset->assignedTo) { + + $target = $asset->assignedTo; + $checkin_at = date('Y-m-d H:i:s'); + $originalValues = $asset->getRawOriginal(); + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues)); + DB::table('assets') + ->where('id', $asset->id) + ->update(['assigned_to' => null]); + } $asset->delete(); diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php index 6e9866f90b..e772bec4df 100644 --- a/app/Http/Controllers/Api/CategoriesController.php +++ b/app/Http/Controllers/Api/CategoriesController.php @@ -43,6 +43,7 @@ class CategoriesController extends Controller $categories = Category::select([ 'id', + 'created_by', 'created_at', 'updated_at', 'name', 'category_type', @@ -50,8 +51,10 @@ class CategoriesController extends Controller 'eula_text', 'require_acceptance', 'checkin_email', - 'image' - ])->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count'); + 'image', + ]) + ->with('adminuser') + ->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count'); /* @@ -91,13 +94,33 @@ class CategoriesController extends Controller $categories->where('checkin_email', '=', $request->input('checkin_email')); } + if ($request->filled('created_by')) { + $categories->where('created_by', '=', $request->input('created_by')); + } + + if ($request->filled('created_at')) { + $categories->where('created_at', '=', $request->input('created_at')); + } + + if ($request->filled('updated_at')) { + $categories->where('updated_at', '=', $request->input('updated_at')); + } + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count'; - $categories->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets_count'; + + switch ($sort_override) { + case 'created_by': + $categories = $categories->OrderByCreatedBy($order); + break; + default: + $categories = $categories->orderBy($column_sort, $order); + break; + } $total = $categories->count(); $categories = $categories->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php index 0d78df9acc..5ba342db33 100644 --- a/app/Http/Controllers/Api/CompaniesController.php +++ b/app/Http/Controllers/Api/CompaniesController.php @@ -42,7 +42,7 @@ class CompaniesController extends Controller $companies = Company::withCount(['assets as assets_count' => function ($query) { $query->AssetsForShow(); - }])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count'); + }])->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count'); if ($request->filled('search')) { $companies->TextSearch($request->input('search')); @@ -56,17 +56,29 @@ class CompaniesController extends Controller $companies->where('email', '=', $request->input('email')); } + if ($request->filled('created_by')) { + $companies->where('created_by', '=', $request->input('created_by')); + } + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value'); $limit = app('api_limit_value'); - - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $companies->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $companies = $companies->OrderByCreatedBy($order); + break; + default: + $companies = $companies->orderBy($column_sort, $order); + break; + } $total = $companies->count(); + $companies = $companies->skip($offset)->take($limit)->get(); return (new CompaniesTransformer)->transformCompanies($companies, $total); diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php index 69bd828487..561e13c9cd 100644 --- a/app/Http/Controllers/Api/ComponentsController.php +++ b/app/Http/Controllers/Api/ComponentsController.php @@ -47,7 +47,7 @@ class ComponentsController extends Controller ]; $components = Component::select('components.*') - ->with('company', 'location', 'category', 'assets', 'supplier'); + ->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser'); if ($request->filled('search')) { $components = $components->TextSearch($request->input('search')); @@ -98,6 +98,9 @@ class ComponentsController extends Controller case 'supplier': $components = $components->OrderSupplier($order); break; + case 'created_by': + $components = $components->OrderByCreatedBy($order); + break; default: $components = $components->orderBy($column_sort, $order); break; @@ -270,7 +273,7 @@ class ComponentsController extends Controller 'component_id' => $component->id, 'created_at' => Carbon::now(), 'assigned_qty' => $request->get('assigned_qty', 1), - 'user_id' => auth()->id(), + 'created_by' => auth()->id(), 'asset_id' => $request->get('assigned_to'), 'note' => $request->get('note'), ]); diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 7be4c3d2dd..8e7f321720 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -92,6 +92,9 @@ class ConsumablesController extends Controller case 'supplier': $consumables = $consumables->OrderSupplier($order); break; + case 'created_by': + $consumables = $consumables->OrderByCreatedBy($order); + break; default: // This array is what determines which fields should be allowed to be sorted on ON the table itself. // These must match a column on the consumables table directly. @@ -210,7 +213,7 @@ class ConsumablesController extends Controller $consumable = Consumable::with(['consumableAssignments'=> function ($query) { $query->orderBy($query->getModel()->getTable().'.created_at', 'DESC'); }, - 'consumableAssignments.admin'=> function ($query) { + 'consumableAssignments.adminuser'=> function ($query) { }, 'consumableAssignments.user'=> function ($query) { }, @@ -228,7 +231,8 @@ class ConsumablesController extends Controller 'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User', 'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'), 'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null, - 'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : null, + 'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response + 'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, ]; } @@ -277,7 +281,7 @@ class ConsumablesController extends Controller $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, - 'user_id' => $user->id, + 'created_by' => $user->id, 'assigned_to' => $request->input('assigned_to'), 'note' => $request->input('note'), ] diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php index eabc79ec2b..e337360cd7 100644 --- a/app/Http/Controllers/Api/DepartmentsController.php +++ b/app/Http/Controllers/Api/DepartmentsController.php @@ -97,7 +97,7 @@ class DepartmentsController extends Controller $department->fill($request->all()); $department = $request->handleImages($department); - $department->user_id = auth()->id(); + $department->created_by = auth()->id(); $department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null); if ($department->save()) { diff --git a/app/Http/Controllers/Api/DepreciationsController.php b/app/Http/Controllers/Api/DepreciationsController.php index 72e0f3a14a..254a72c98e 100644 --- a/app/Http/Controllers/Api/DepreciationsController.php +++ b/app/Http/Controllers/Api/DepreciationsController.php @@ -32,7 +32,8 @@ class DepreciationsController extends Controller 'licenses_count', ]; - $depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','user_id','created_at','updated_at') + $depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','created_at','updated_at', 'created_by') + ->with('adminuser') ->withCount('assets as assets_count') ->withCount('models as models_count') ->withCount('licenses as licenses_count'); @@ -44,10 +45,18 @@ class DepreciationsController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $depreciations->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $depreciations = $depreciations->OrderByCreatedBy($order); + break; + default: + $depreciations = $depreciations->orderBy($column_sort, $order); + break; + } $total = $depreciations->count(); $depreciations = $depreciations->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/Api/GroupsController.php b/app/Http/Controllers/Api/GroupsController.php index 878650c718..81217ce8db 100644 --- a/app/Http/Controllers/Api/GroupsController.php +++ b/app/Http/Controllers/Api/GroupsController.php @@ -23,9 +23,8 @@ class GroupsController extends Controller $this->authorize('superadmin'); $this->authorize('view', Group::class); - $allowed_columns = ['id', 'name', 'created_at', 'users_count']; - $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count'); + $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count'); if ($request->filled('search')) { $groups = $groups->TextSearch($request->input('search')); @@ -35,13 +34,29 @@ class GroupsController extends Controller $groups->where('name', '=', $request->input('name')); } - // Make sure the offset and limit are actually integers and do not exceed system limits + $offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $groups->orderBy($sort, $order); + + switch ($request->input('sort')) { + case 'created_by': + $groups = $groups->OrderByCreatedBy($order); + break; + default: + // This array is what determines which fields should be allowed to be sorted on ON the table itself. + // These must match a column on the consumables table directly. + $allowed_columns = [ + 'id', + 'name', + 'created_at', + 'users_count', + ]; + + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $groups = $groups->orderBy($sort, $order); + break; + } $total = $groups->count(); $groups = $groups->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php index a9630aa296..2ed7097322 100644 --- a/app/Http/Controllers/Api/LicenseSeatsController.php +++ b/app/Http/Controllers/Api/LicenseSeatsController.php @@ -107,7 +107,7 @@ class LicenseSeatsController extends Controller // attempt to update the license seat $licenseSeat->fill($request->all()); - $licenseSeat->user_id = auth()->id(); + $licenseSeat->created_by = auth()->id(); // check if this update is a checkin operation // 1. are relevant fields touched at all? diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index 0dae68dbb7..db39f987aa 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -70,8 +70,8 @@ class LicensesController extends Controller $licenses->where('depreciation_id', '=', $request->input('depreciation_id')); } - if ($request->filled('user_id')) { - $licenses->where('user_id', '=', $request->input('user_id')); + if ($request->filled('created_by')) { + $licenses->where('created_by', '=', $request->input('created_by')); } if (($request->filled('maintained')) && ($request->input('maintained')=='true')) { @@ -117,7 +117,7 @@ class LicensesController extends Controller $licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order); break; case 'created_by': - $licenses = $licenses->OrderCreatedBy($order); + $licenses = $licenses->OrderByCreatedBy($order); break; default: $allowed_columns = @@ -182,7 +182,7 @@ class LicensesController extends Controller public function show($id) : JsonResponse | array { $this->authorize('view', License::class); - $license = License::withCount('freeSeats')->findOrFail($id); + $license = License::withCount('freeSeats as free_seats_count')->findOrFail($id); $license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset'); return (new LicensesTransformer)->transformLicense($license); @@ -220,7 +220,6 @@ class LicensesController extends Controller */ public function destroy($id) : JsonResponse { - // $license = License::findOrFail($id); $this->authorize('delete', $license); diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php index eb89693e5c..f111ef6c83 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -25,11 +25,42 @@ class ManufacturersController extends Controller public function index(Request $request) : JsonResponse | array { $this->authorize('view', Manufacturer::class); - $allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count']; + $allowed_columns = [ + 'id', + 'name', + 'url', + 'support_url', + 'support_email', + 'warranty_lookup_url', + 'support_phone', + 'created_at', + 'updated_at', + 'image', + 'assets_count', + 'consumables_count', + 'components_count', + 'licenses_count' + ]; - $manufacturers = Manufacturer::select( - ['id', 'name', 'url', 'support_url', 'warranty_lookup_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at'] - )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count'); + $manufacturers = Manufacturer::select([ + 'id', + 'name', + 'url', + 'support_url', + 'warranty_lookup_url', + 'support_email', + 'support_phone', + 'created_by', + 'created_at', + 'updated_at', + 'image', + 'deleted_at', + ]) + ->with('adminuser') + ->withCount('assets as assets_count') + ->withCount('licenses as licenses_count') + ->withCount('consumables as consumables_count') + ->withCount('accessories as accessories_count'); if ($request->input('deleted') == 'true') { $manufacturers->onlyTrashed(); @@ -66,10 +97,18 @@ class ManufacturersController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $manufacturers->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $manufacturers = $manufacturers->OrderByCreatedBy($order); + break; + default: + $manufacturers = $manufacturers->orderBy($column_sort, $order); + break; + } $total = $manufacturers->count(); $manufacturers = $manufacturers->skip($offset)->take($limit)->get(); @@ -181,7 +220,7 @@ class ManufacturersController extends Controller $logaction->item_type = Manufacturer::class; $logaction->item_id = $manufacturer->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200); diff --git a/app/Http/Controllers/Api/PredefinedKitsController.php b/app/Http/Controllers/Api/PredefinedKitsController.php index 26ccb50354..24f1320185 100644 --- a/app/Http/Controllers/Api/PredefinedKitsController.php +++ b/app/Http/Controllers/Api/PredefinedKitsController.php @@ -23,9 +23,8 @@ class PredefinedKitsController extends Controller public function index(Request $request) : JsonResponse | array { $this->authorize('view', PredefinedKit::class); - $allowed_columns = ['id', 'name']; - $kits = PredefinedKit::query(); + $kits = PredefinedKit::query()->with('adminuser'); if ($request->filled('search')) { $kits = $kits->TextSearch($request->input('search')); @@ -36,8 +35,25 @@ class PredefinedKitsController extends Controller $limit = app('api_limit_value'); $order = $request->input('order') === 'desc' ? 'desc' : 'asc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name'; - $kits->orderBy($sort, $order); + + switch ($request->input('sort')) { + case 'created_by': + $kits = $kits->OrderByCreatedBy($order); + break; + default: + // This array is what determines which fields should be allowed to be sorted on ON the table itself. + // These must match a column on the consumables table directly. + $allowed_columns = [ + 'id', + 'name', + 'created_at', + 'updated_at', + ]; + + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $kits = $kits->orderBy($sort, $order); + break; + } $total = $kits->count(); $kits = $kits->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php index 931886fb29..566911dd27 100644 --- a/app/Http/Controllers/Api/ReportsController.php +++ b/app/Http/Controllers/Api/ReportsController.php @@ -20,7 +20,7 @@ class ReportsController extends Controller { $this->authorize('reports.view'); - $actionlogs = Actionlog::with('item', 'user', 'admin', 'target', 'location'); + $actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location'); if ($request->filled('search')) { $actionlogs = $actionlogs->TextSearch(e($request->input('search'))); @@ -48,8 +48,8 @@ class ReportsController extends Controller $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc'); } - if ($request->filled('user_id')) { - $actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id')); + if ($request->filled('created_by')) { + $actionlogs = $actionlogs->where('created_by', '=', $request->input('created_by')); } if ($request->filled('action_source')) { @@ -68,13 +68,14 @@ class ReportsController extends Controller 'id', 'created_at', 'target_id', - 'user_id', + 'created_by', 'accept_signature', 'action_type', 'note', 'remote_ip', 'user_agent', 'action_source', + 'action_date', ]; @@ -86,8 +87,8 @@ class ReportsController extends Controller $order = ($request->input('order') == 'asc') ? 'asc' : 'desc'; switch ($request->input('sort')) { - case 'admin': - $actionlogs->OrderAdmin($order); + case 'created_by': + $actionlogs->OrderByCreatedBy($order); break; default: $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php index ce61d653f5..754ebf7323 100644 --- a/app/Http/Controllers/Api/StatuslabelsController.php +++ b/app/Http/Controllers/Api/StatuslabelsController.php @@ -25,9 +25,17 @@ class StatuslabelsController extends Controller public function index(Request $request) : array { $this->authorize('view', Statuslabel::class); - $allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label']; + $allowed_columns = [ + 'id', + 'name', + 'created_at', + 'assets_count', + 'color', + 'notes', + 'default_label' + ]; - $statuslabels = Statuslabel::withCount('assets as assets_count'); + $statuslabels = Statuslabel::with('adminuser')->withCount('assets as assets_count'); if ($request->filled('search')) { $statuslabels = $statuslabels->TextSearch($request->input('search')); @@ -54,10 +62,18 @@ class StatuslabelsController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $statuslabels->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $statuslabels = $statuslabels->OrderByCreatedBy($order); + break; + default: + $statuslabels = $statuslabels->orderBy($column_sort, $order); + break; + } $total = $statuslabels->count(); $statuslabels = $statuslabels->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 7a126ba97a..4714b29ea3 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -42,13 +42,14 @@ class UsersController extends Controller $users = User::select([ 'users.activated', - 'users.created_by', 'users.address', 'users.avatar', 'users.city', 'users.company_id', 'users.country', + 'users.created_by', 'users.created_at', + 'users.updated_at', 'users.deleted_at', 'users.department_id', 'users.email', @@ -67,7 +68,6 @@ class UsersController extends Controller 'users.state', 'users.two_factor_enrolled', 'users.two_factor_optin', - 'users.updated_at', 'users.username', 'users.zip', 'users.remote', @@ -255,6 +255,7 @@ class UsersController extends Controller 'groups', 'activated', 'created_at', + 'updated_at', 'two_factor_enrolled', 'two_factor_optin', 'last_login', @@ -691,7 +692,7 @@ class UsersController extends Controller $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('2FA reset'); return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200); @@ -741,7 +742,7 @@ class UsersController extends Controller $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200); diff --git a/app/Http/Controllers/AssetMaintenancesController.php b/app/Http/Controllers/AssetMaintenancesController.php index 02be1e6061..360db45262 100644 --- a/app/Http/Controllers/AssetMaintenancesController.php +++ b/app/Http/Controllers/AssetMaintenancesController.php @@ -109,7 +109,7 @@ class AssetMaintenancesController extends Controller $assetMaintenance->title = $request->input('title'); $assetMaintenance->start_date = $request->input('start_date'); $assetMaintenance->completion_date = $request->input('completion_date'); - $assetMaintenance->user_id = Auth::id(); + $assetMaintenance->created_by = auth()->id(); if (($assetMaintenance->completion_date !== null) && ($assetMaintenance->start_date !== '') diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 94c630c094..9d4c13afd9 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -78,7 +78,7 @@ class AssetModelsController extends Controller $model->manufacturer_id = $request->input('manufacturer_id'); $model->category_id = $request->input('category_id'); $model->notes = $request->input('notes'); - $model->user_id = Auth::id(); + $model->created_by = auth()->id(); $model->requestable = $request->has('requestable'); if ($request->input('fieldset_id') != '') { @@ -237,7 +237,7 @@ class AssetModelsController extends Controller $logaction->item_type = AssetModel::class; $logaction->item_id = $model->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 5f944c386e..dda54f4c8d 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -133,7 +133,7 @@ class AssetsController extends Controller $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset->notes = $request->input('notes'); - $asset->user_id = Auth::id(); + $asset->created_by = auth()->id(); $asset->status_id = request('status_id'); $asset->warranty_months = request('warranty_months', null); $asset->purchase_cost = request('purchase_cost'); @@ -329,9 +329,7 @@ class AssetsController extends Controller } $asset->supplier_id = $request->input('supplier_id', null); $asset->expected_checkin = $request->input('expected_checkin', null); - - // If the box isn't checked, it's not in the request at all. - $asset->requestable = $request->filled('requestable', 0); + $asset->requestable = $request->input('requestable', 0); $asset->rtd_location_id = $request->input('rtd_location_id', null); $asset->byod = $request->input('byod', 0); @@ -430,7 +428,7 @@ class AssetsController extends Controller * @param int $assetId * @since [v1.0] */ - public function destroy($assetId) : RedirectResponse + public function destroy(Request $request, $assetId) : RedirectResponse { // Check if the asset exists if (is_null($asset = Asset::find($assetId))) { @@ -440,9 +438,17 @@ class AssetsController extends Controller $this->authorize('delete', $asset); - DB::table('assets') - ->where('id', $asset->id) - ->update(['assigned_to' => null]); + if ($asset->assignedTo) { + + $target = $asset->assignedTo; + $checkin_at = date('Y-m-d H:i:s'); + $originalValues = $asset->getRawOriginal(); + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues)); + DB::table('assets') + ->where('id', $asset->id) + ->update(['assigned_to' => null]); + } + if ($asset->image) { try { @@ -747,7 +753,7 @@ class AssetsController extends Controller Actionlog::firstOrCreate([ 'item_id' => $asset->id, 'item_type' => Asset::class, - 'user_id' => auth()->id(), + 'created_by' => auth()->id(), 'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer', 'target_id' => $item[$asset_tag][$batch_counter]['user_id'], 'target_type' => User::class, @@ -775,7 +781,7 @@ class AssetsController extends Controller Actionlog::firstOrCreate([ 'item_id' => $item[$asset_tag][$batch_counter]['asset_id'], 'item_type' => Asset::class, - 'user_id' => auth()->id(), + 'created_by' => auth()->id(), 'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer', 'target_id' => null, 'created_at' => $checkin_date, diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index d58edbacab..1ce08e65e9 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -10,6 +10,7 @@ use App\Models\AssetModel; use App\Models\Statuslabel; use App\Models\Setting; use App\View\Label; +use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; @@ -271,6 +272,23 @@ class BulkAssetsController extends Controller $this->conditionallyAddItem($custom_field_column); } + if (!($asset->eol_explicit)) { + if ($request->filled('model_id')) { + $model = AssetModel::find($request->input('model_id')); + if ($model->eol > 0) { + if ($request->filled('purchase_date')) { + $this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($model->eol)->format('Y-m-d'); + } else { + $this->update_array['asset_eol_date'] = Carbon::parse($asset->purchase_date)->addMonths($model->eol)->format('Y-m-d'); + } + } else { + $this->update_array['asset_eol_date'] = null; + } + } elseif (($request->filled('purchase_date')) && ($asset->model->eol > 0)) { + $this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); + } + } + /** * Blank out fields that were requested to be blanked out via checkbox */ @@ -281,6 +299,9 @@ class BulkAssetsController extends Controller if ($request->input('null_purchase_date')=='1') { $this->update_array['purchase_date'] = null; + if (!($asset->eol_explicit)) { + $this->update_array['asset_eol_date'] = null; + } } if ($request->input('null_expected_checkin_date')=='1') { diff --git a/app/Http/Controllers/CategoriesController.php b/app/Http/Controllers/CategoriesController.php index ac57ad6a6d..93b3d4a0d0 100755 --- a/app/Http/Controllers/CategoriesController.php +++ b/app/Http/Controllers/CategoriesController.php @@ -69,7 +69,7 @@ class CategoriesController extends Controller $category->use_default_eula = $request->input('use_default_eula', '0'); $category->require_acceptance = $request->input('require_acceptance', '0'); $category->checkin_email = $request->input('checkin_email', '0'); - $category->user_id = Auth::id(); + $category->created_by = auth()->id(); $category = $request->handleImages($category); if ($category->save()) { diff --git a/app/Http/Controllers/CompaniesController.php b/app/Http/Controllers/CompaniesController.php index 589832af72..238ffc85f5 100644 --- a/app/Http/Controllers/CompaniesController.php +++ b/app/Http/Controllers/CompaniesController.php @@ -60,6 +60,7 @@ final class CompaniesController extends Controller $company->phone = $request->input('phone'); $company->fax = $request->input('fax'); $company->email = $request->input('email'); + $company->created_by = auth()->id(); $company = $request->handleImages($company); diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php index e9db70811c..b40d592369 100644 --- a/app/Http/Controllers/Components/ComponentCheckoutController.php +++ b/app/Http/Controllers/Components/ComponentCheckoutController.php @@ -106,7 +106,7 @@ class ComponentCheckoutController extends Controller $component->asset_id = $request->input('asset_id'); $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'user_id' => auth()->user()->id, + 'created_by' => auth()->user()->id, 'created_at' => date('Y-m-d H:i:s'), 'assigned_qty' => $request->input('assigned_qty'), 'asset_id' => $request->input('asset_id'), diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php index 57cd0a2b45..430984767e 100644 --- a/app/Http/Controllers/Components/ComponentsController.php +++ b/app/Http/Controllers/Components/ComponentsController.php @@ -81,7 +81,7 @@ class ComponentsController extends Controller $component->purchase_date = $request->input('purchase_date', null); $component->purchase_cost = $request->input('purchase_cost', null); $component->qty = $request->input('qty'); - $component->user_id = Auth::id(); + $component->created_by = auth()->id(); $component->notes = $request->input('notes'); $component = $request->handleImages($component); diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index 1bdb16af92..3bf202733a 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -95,7 +95,7 @@ class ConsumableCheckoutController extends Controller for($i = 0; $i < $quantity; $i++){ $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, - 'user_id' => $admin_user->id, + 'created_by' => $admin_user->id, 'assigned_to' => e($request->input('assigned_to')), 'note' => $request->input('note'), ]); diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index 42c0766fe0..98141f2783 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -81,7 +81,7 @@ class ConsumablesController extends Controller $consumable->purchase_date = $request->input('purchase_date'); $consumable->purchase_cost = $request->input('purchase_cost'); $consumable->qty = $request->input('qty'); - $consumable->user_id = Auth::id(); + $consumable->created_by = auth()->id(); $consumable->notes = $request->input('notes'); @@ -221,7 +221,7 @@ class ConsumablesController extends Controller $consumable = clone $consumable_to_close; $consumable->id = null; $consumable->image = null; - $consumable->user_id = null; + $consumable->created_by = null; return view('consumables/edit')->with('item', $consumable); } diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index 42f6c212db..5a0dc6aec2 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -104,7 +104,7 @@ class CustomFieldsController extends Controller "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), "show_in_listview" => $request->get("show_in_listview", 0), "show_in_requestable_list" => $request->get("show_in_requestable_list", 0), - "user_id" => Auth::id() + "user_id" => auth()->id() ]); @@ -248,7 +248,7 @@ class CustomFieldsController extends Controller $field->name = trim(e($request->get("name"))); $field->element = e($request->get("element")); $field->field_values = $request->get("field_values"); - $field->user_id = Auth::id(); + $field->created_by = auth()->id(); $field->help_text = $request->get("help_text"); $field->show_in_email = $show_in_email; $field->is_unique = $request->get("is_unique", 0); diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index 8b9844d152..1d887db29a 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -90,7 +90,7 @@ class CustomFieldsetsController extends Controller $fieldset = new CustomFieldset([ 'name' => $request->get('name'), - 'user_id' => auth()->id(), + 'created_by' => auth()->id(), ]); $validator = Validator::make($request->all(), $fieldset->rules); diff --git a/app/Http/Controllers/DepartmentsController.php b/app/Http/Controllers/DepartmentsController.php index 5818435deb..287315ef2c 100644 --- a/app/Http/Controllers/DepartmentsController.php +++ b/app/Http/Controllers/DepartmentsController.php @@ -51,7 +51,7 @@ class DepartmentsController extends Controller $this->authorize('create', Department::class); $department = new Department; $department->fill($request->all()); - $department->user_id = auth()->id(); + $department->created_by = auth()->id(); $department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null); $department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null); $department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null); diff --git a/app/Http/Controllers/DepreciationsController.php b/app/Http/Controllers/DepreciationsController.php index 888f7a7e77..5f4a5ca10d 100755 --- a/app/Http/Controllers/DepreciationsController.php +++ b/app/Http/Controllers/DepreciationsController.php @@ -61,7 +61,7 @@ class DepreciationsController extends Controller // Depreciation data $depreciation->name = $request->input('name'); $depreciation->months = $request->input('months'); - $depreciation->user_id = Auth::id(); + $depreciation->created_by = auth()->id(); $request->validate([ 'depreciation_min' => [ diff --git a/app/Http/Controllers/HealthController.php b/app/Http/Controllers/HealthController.php index c75b903b0b..dac1f17bf4 100644 --- a/app/Http/Controllers/HealthController.php +++ b/app/Http/Controllers/HealthController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Facades\DB; /** * This controller provide the health route for @@ -15,13 +16,35 @@ use Illuminate\Routing\Controller as BaseController; */ class HealthController extends BaseController { + + public function __construct() + { + $this->middleware('health'); + } + + /** * Returns a fixed JSON content ({ "status": "ok"}) which indicate the app is up and running */ public function get() { - return response()->json([ - 'status' => 'ok', - ]); + try { + + if (DB::select('select 2 + 2')) { + return response()->json([ + 'status' => 'ok', + ]); + } + + } catch (\Exception $e) { + \Log::error('Could not connect to database'); + return response()->json([ + 'status' => 'database connection failed', + ], 500); + + } + + + } } diff --git a/app/Http/Controllers/Kits/PredefinedKitsController.php b/app/Http/Controllers/Kits/PredefinedKitsController.php index 187f5aad14..54f7514510 100644 --- a/app/Http/Controllers/Kits/PredefinedKitsController.php +++ b/app/Http/Controllers/Kits/PredefinedKitsController.php @@ -55,6 +55,7 @@ class PredefinedKitsController extends Controller // Create a new Predefined Kit $kit = new PredefinedKit; $kit->name = $request->input('name'); + $kit->created_by = auth()->id(); if (! $kit->save()) { return redirect()->back()->withInput()->withErrors($kit->getErrors()); diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index c08980fc06..0f31db1449 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -77,7 +77,7 @@ class LicenseCheckoutController extends Controller $this->authorize('checkout', $license); $licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId); - $licenseSeat->user_id = Auth::id(); + $licenseSeat->created_by = auth()->id(); $licenseSeat->notes = $request->input('notes'); diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php index 7a51344dd0..6098423ba3 100755 --- a/app/Http/Controllers/Licenses/LicensesController.php +++ b/app/Http/Controllers/Licenses/LicensesController.php @@ -99,7 +99,7 @@ class LicensesController extends Controller $license->supplier_id = $request->input('supplier_id'); $license->category_id = $request->input('category_id'); $license->termination_date = $request->input('termination_date'); - $license->user_id = Auth::id(); + $license->created_by = auth()->id(); $license->min_amt = $request->input('min_amt'); session()->put(['redirect_option' => $request->get('redirect_option')]); diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index f32e6b8489..75abce97ed 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -75,7 +75,7 @@ class LocationsController extends Controller $location->zip = $request->input('zip'); $location->ldap_ou = $request->input('ldap_ou'); $location->manager_id = $request->input('manager_id'); - $location->user_id = auth()->id(); + $location->created_by = auth()->id(); $location->phone = request('phone'); $location->fax = request('fax'); @@ -278,7 +278,7 @@ class LocationsController extends Controller $logaction->item_type = Location::class; $logaction->item_id = $location->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success')); diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php index 8e979e3896..68124f644c 100755 --- a/app/Http/Controllers/ManufacturersController.php +++ b/app/Http/Controllers/ManufacturersController.php @@ -61,7 +61,7 @@ class ManufacturersController extends Controller $this->authorize('create', Manufacturer::class); $manufacturer = new Manufacturer; $manufacturer->name = $request->input('name'); - $manufacturer->user_id = Auth::id(); + $manufacturer->created_by = auth()->id(); $manufacturer->url = $request->input('url'); $manufacturer->support_url = $request->input('support_url'); $manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url'); @@ -219,7 +219,7 @@ class ManufacturersController extends Controller $logaction->item_type = Manufacturer::class; $logaction->item_id = $manufacturer->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); // Redirect them to the deleted page if there are more, otherwise the section index diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index abe09e8afa..896c253463 100755 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -194,14 +194,14 @@ class ProfileController extends Controller */ public function printInventory() : View { - $show_user = auth()->user(); + $show_users = User::where('id',auth()->user()->id)->get(); return view('users/print') - ->with('assets', auth()->user()->assets) - ->with('licenses', $show_user->licenses()->get()) - ->with('accessories', $show_user->accessories()->get()) - ->with('consumables', $show_user->consumables()->get()) - ->with('show_user', $show_user) + ->with('assets', auth()->user()->assets()) + ->with('licenses', auth()->user()->licenses()->get()) + ->with('accessories', auth()->user()->accessories()->get()) + ->with('consumables', auth()->user()->consumables()->get()) + ->with('users', $show_users) ->with('settings', Setting::getSettings()); } @@ -222,7 +222,12 @@ class ProfileController extends Controller return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email')); } - $user->notify((new CurrentInventory($user))); + try { + $user->notify((new CurrentInventory($user))); + } catch (\Exception $e) { + \Log::error($e); + } + return redirect()->back()->with('success', trans('admin/users/general.user_notified')); } } diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 6ca919b2f4..ec8fdc1df9 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -716,6 +716,10 @@ class ReportsController extends Controller $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]); } + if (($request->filled('asset_eol_date_start')) && ($request->filled('asset_eol_date_end'))) { + $assets->whereBetween('assets.asset_eol_date', [$request->input('asset_eol_date_start'), $request->input('asset_eol_date_end')]); + } + if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) { $last_audit_start = Carbon::parse($request->input('last_audit_start'))->startOfDay(); $last_audit_end = Carbon::parse($request->input('last_audit_end'))->endOfDay(); @@ -791,7 +795,7 @@ class ReportsController extends Controller } if ($request->filled('eol')) { - $row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : ''; + $row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : ''; } if ($request->filled('order')) { diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 31b4179b4a..aa773d9eea 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -7,6 +7,11 @@ use App\Helpers\StorageHelper; use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\SettingsSamlRequest; use App\Http\Requests\SetupUserRequest; +use App\Http\Requests\StoreLdapSettings; +use App\Http\Requests\StoreLocalizationSettings; +use App\Http\Requests\StoreNotificationSettings; +use App\Http\Requests\StoreLabelSettings; +use App\Http\Requests\StoreSecuritySettings; use App\Models\CustomField; use App\Models\Group; use App\Models\Setting; @@ -181,7 +186,7 @@ class SettingsController extends Controller $settings->brand = 1; $settings->locale = $request->input('locale', 'en-US'); $settings->default_currency = $request->input('default_currency', 'USD'); - $settings->user_id = 1; + $settings->created_by = 1; $settings->email_domain = $request->input('email_domain'); $settings->email_format = $request->input('email_format'); $settings->next_auto_tag_base = 1; @@ -273,20 +278,6 @@ class SettingsController extends Controller return view('settings/index', compact('settings')); } - /** - * Return the admin settings page. - * - * @author [A. Gianotto] [] - * - * @since [v1.0] - */ - public function getEdit() : View - - { - $setting = Setting::getSettings(); - - return view('settings/general', compact('setting')); - } /** * Return a form to allow a super admin to update settings. @@ -486,7 +477,7 @@ class SettingsController extends Controller * * @since [v1.0] */ - public function postSecurity(Request $request) : RedirectResponse + public function postSecurity(StoreSecuritySettings $request) : RedirectResponse { $this->validate($request, [ 'pwd_secure_complexity' => 'array', @@ -556,7 +547,7 @@ class SettingsController extends Controller * * @since [v1.0] */ - public function postLocalization(Request $request) : RedirectResponse + public function postLocalization(StoreLocalizationSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -599,7 +590,7 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] */ - public function postAlerts(Request $request) : RedirectResponse + public function postAlerts(StoreNotificationSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -780,7 +771,7 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] */ - public function postLabels(Request $request) : RedirectResponse + public function postLabels(StoreLabelSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -859,26 +850,7 @@ class SettingsController extends Controller { $setting = Setting::getSettings(); $groups = Group::pluck('name', 'id'); - - - /** - * This validator is only temporary (famous last words.) - @snipe - */ - $messages = [ - 'ldap_username_field.not_in' => 'sAMAccountName (mixed case) will likely not work. You should use samaccountname (lowercase) instead. ', - 'ldap_auth_filter_query.not_in' => 'uid=samaccountname is probably not a valid auth filter. You probably want uid= ', - 'ldap_filter.regex' => 'This value should probably not be wrapped in parentheses.', - ]; - - $validator = Validator::make($setting->toArray(), [ - 'ldap_username_field' => 'not_in:sAMAccountName', - 'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1', - 'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1', - ], $messages); - - - - return view('settings.ldap', compact('setting', 'groups'))->withErrors($validator); + return view('settings.ldap', compact('setting', 'groups')); } /** @@ -887,7 +859,7 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] */ - public function postLdapSettings(Request $request) : RedirectResponse + public function postLdapSettings(StoreLdapSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); diff --git a/app/Http/Controllers/StatuslabelsController.php b/app/Http/Controllers/StatuslabelsController.php index 535117e97f..21a7c798b9 100755 --- a/app/Http/Controllers/StatuslabelsController.php +++ b/app/Http/Controllers/StatuslabelsController.php @@ -69,7 +69,7 @@ class StatuslabelsController extends Controller // Save the Statuslabel data $statusLabel->name = $request->input('name'); - $statusLabel->user_id = Auth::id(); + $statusLabel->created_by = auth()->id(); $statusLabel->notes = $request->input('notes'); $statusLabel->deployable = $statusType['deployable']; $statusLabel->pending = $statusType['pending']; diff --git a/app/Http/Controllers/SuppliersController.php b/app/Http/Controllers/SuppliersController.php index e96e32b84f..605bb66f6d 100755 --- a/app/Http/Controllers/SuppliersController.php +++ b/app/Http/Controllers/SuppliersController.php @@ -62,7 +62,7 @@ class SuppliersController extends Controller $supplier->email = request('email'); $supplier->notes = request('notes'); $supplier->url = $supplier->addhttp(request('url')); - $supplier->user_id = Auth::id(); + $supplier->created_by = auth()->id(); $supplier = $request->handleImages($supplier); if ($supplier->save()) { diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index a848f3808b..fbf08c9820 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -13,6 +13,7 @@ use App\Models\Group; use App\Models\LicenseSeat; use App\Models\ConsumableAssignment; use App\Models\Consumable; +use App\Models\Setting; use App\Models\User; use Carbon\Carbon; use Illuminate\Http\Request; @@ -35,7 +36,7 @@ class BulkUsersController extends Controller */ public function edit(Request $request) { - $this->authorize('update', User::class); + $this->authorize('view', User::class); // Make sure there were users selected if (($request->filled('ids')) && (count($request->input('ids')) > 0)) { @@ -47,16 +48,18 @@ class BulkUsersController extends Controller // bulk edit, display the bulk edit form if ($request->input('bulk_actions') == 'edit') { + $this->authorize('update', User::class); return view('users/bulk-edit', compact('users')) ->with('groups', Group::pluck('name', 'id')); // bulk delete, display the bulk delete confirmation form } elseif ($request->input('bulk_actions') == 'delete') { + $this->authorize('delete', User::class); return view('users/confirm-bulk-delete')->with('users', $users)->with('statuslabel_list', Helper::statusLabelList()); // merge, confirm they have at least 2 users selected and display the merge screen } elseif ($request->input('bulk_actions') == 'merge') { - + $this->authorize('delete', User::class); if (($request->filled('ids')) && (count($request->input('ids')) > 1)) { return view('users/confirm-merge')->with('users', $users); // Not enough users selected, send them back @@ -76,6 +79,33 @@ class BulkUsersController extends Controller } return redirect()->back()->with('success', trans('admin/users/message.password_resets_sent')); + } elseif ($request->input('bulk_actions') == 'print') { + $users = User::query() + ->with([ + 'assets.assetlog', + 'assets.assignedAssets.assetlog', + 'assets.assignedAssets.defaultLoc', + 'assets.assignedAssets.location', + 'assets.assignedAssets.model.category', + 'assets.defaultLoc', + 'assets.location', + 'assets.model.category', + 'accessories.assetlog', + 'accessories.category', + 'accessories.manufacturer', + 'consumables.assetlog', + 'consumables.category', + 'consumables.manufacturer', + 'licenses.category', + ]) + ->withTrashed() + ->findMany($request->input('ids')); + + $users->each(fn($user) => $this->authorize('view', $user)); + + return view('users.print') + ->with('users', $users) + ->with('settings', Setting::getSettings()); } } @@ -101,7 +131,7 @@ class BulkUsersController extends Controller $user_raw_array = $request->input('ids'); // Remove the user from any updates. - $user_raw_array = array_diff($user_raw_array, [Auth::id()]); + $user_raw_array = array_diff($user_raw_array, [auth()->id()]); $manager_conflict = false; $users = User::whereIn('id', $user_raw_array)->where('id', '!=', auth()->id())->get(); @@ -166,7 +196,7 @@ class BulkUsersController extends Controller } // Save the updated info User::whereIn('id', $user_raw_array) - ->where('id', '!=', Auth::id())->update($this->update_array); + ->where('id', '!=', auth()->id())->update($this->update_array); if (array_key_exists('location_id', $this->update_array)){ Asset::where('assigned_type', User::class) @@ -228,7 +258,7 @@ class BulkUsersController extends Controller $user_raw_array = request('ids'); - if (($key = array_search(Auth::id(), $user_raw_array)) !== false) { + if (($key = array_search(auth()->id(), $user_raw_array)) !== false) { unset($user_raw_array[$key]); } @@ -293,7 +323,7 @@ class BulkUsersController extends Controller $logAction->item_type = $itemType; $logAction->target_id = $item->assigned_to; $logAction->target_type = User::class; - $logAction->user_id = Auth::id(); + $logAction->created_at = auth()->id(); $logAction->note = 'Bulk checkin items'; $logAction->logaction('checkin from'); } @@ -307,7 +337,7 @@ class BulkUsersController extends Controller $logAction->item_type = Accessory::class; $logAction->target_id = $accessoryUserRow->assigned_to; $logAction->target_type = User::class; - $logAction->user_id = Auth::id(); + $logAction->created_at = auth()->id(); $logAction->note = 'Bulk checkin items'; $logAction->logaction('checkin from'); } @@ -321,7 +351,7 @@ class BulkUsersController extends Controller $logAction->item_type = Consumable::class; $logAction->target_id = $consumableUserRow->assigned_to; $logAction->target_type = User::class; - $logAction->user_id = Auth::id(); + $logAction->created_at = auth()->id(); $logAction->note = 'Bulk checkin items'; $logAction->logaction('checkin from'); } diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php index ded44f35f6..9e5f322c03 100644 --- a/app/Http/Controllers/Users/UserFilesController.php +++ b/app/Http/Controllers/Users/UserFilesController.php @@ -46,7 +46,7 @@ class UserFilesController extends Controller $logAction = new Actionlog(); $logAction->item_id = $user->id; $logAction->item_type = User::class; - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->note = $request->input('notes'); $logAction->target_id = null; $logAction->created_at = date("Y-m-d H:i:s"); diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 1d7fc91ebd..051db1f4ef 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -372,7 +372,7 @@ class UsersController extends Controller $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = auth()->id(); + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); // Redirect them to the deleted page if there are more, otherwise the section index @@ -597,23 +597,37 @@ class UsersController extends Controller public function printInventory($id) { $this->authorize('view', User::class); - if ($user = User::where('id', $id)->withTrashed()->first()) { + $user = User::where('id', $id) + ->with([ + 'assets.assetlog', + 'assets.assignedAssets.assetlog', + 'assets.assignedAssets.defaultLoc', + 'assets.assignedAssets.location', + 'assets.assignedAssets.model.category', + 'assets.defaultLoc', + 'assets.location', + 'assets.model.category', + 'accessories.assetlog', + 'accessories.category', + 'accessories.manufacturer', + 'consumables.assetlog', + 'consumables.category', + 'consumables.manufacturer', + 'licenses.category', + ]) + ->withTrashed() + ->first(); + + if ($user) { $this->authorize('view', $user); - $assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get(); - $accessories = $user->accessories()->get(); - $consumables = $user->consumables()->get(); - return view('users/print')->with('assets', $assets) - ->with('licenses', $user->licenses()->get()) - ->with('accessories', $accessories) - ->with('consumables', $consumables) - ->with('show_user', $user) + return view('users.print') + ->with('users', [$user]) ->with('settings', Setting::getSettings()); } return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id'))); - } /** diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php index db4afc8322..12c300e5bd 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -13,6 +13,7 @@ use App\Notifications\RequestAssetNotification; use Illuminate\Http\Request; use Illuminate\Http\RedirectResponse; use \Illuminate\Contracts\View\View; +use Log; /** * This controller handles all actions related to the ability for users @@ -179,8 +180,11 @@ class ViewAssetsController extends Controller $asset->decrement('requests_counter', 1); $logaction->logaction('request canceled'); - $settings->notify(new RequestAssetCancelation($data)); - + try { + $settings->notify(new RequestAssetCancelation($data)); + } catch (\Exception $e) { + Log::warning($e); + } return redirect()->route('requestable-assets') ->with('success')->with('success', trans('admin/hardware/message.requests.canceled')); } @@ -188,7 +192,11 @@ class ViewAssetsController extends Controller $logaction->logaction('requested'); $asset->request(); $asset->increment('requests_counter', 1); - $settings->notify(new RequestAssetNotification($data)); + try { + $settings->notify(new RequestAssetNotification($data)); + } catch (\Exception $e) { + Log::warning($e); + } return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 8c9289a799..b69e22e4f9 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -53,6 +53,10 @@ class Kernel extends HttpKernel \App\Http\Middleware\CheckLocale::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], + + 'health' => [ + + ], ]; /** @@ -69,5 +73,6 @@ class Kernel extends HttpKernel 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'health' => null, ]; } diff --git a/app/Http/Middleware/CheckForSetup.php b/app/Http/Middleware/CheckForSetup.php index 4e399ffcff..6cb593b5ff 100644 --- a/app/Http/Middleware/CheckForSetup.php +++ b/app/Http/Middleware/CheckForSetup.php @@ -7,14 +7,19 @@ use Closure; class CheckForSetup { + + protected $except = [ + '_debugbar*', + 'health' + ]; + public function handle($request, Closure $next, $guard = null) { /** - * This is dumb - * @todo Check on removing this, not sure if it's still needed + * Skip this middleware for the debugbar and health check */ - if ($request->is('_debugbar*')) { + if ($request->is($this->except)) { return $next($request); } @@ -25,7 +30,7 @@ class CheckForSetup return $next($request); } } else { - if (! ($request->is('setup*')) && ! ($request->is('.env')) && ! ($request->is('health'))) { + if (! ($request->is('setup*')) && ! ($request->is('.env'))) { return redirect(config('app.url').'/setup'); } diff --git a/app/Http/Requests/ItemImportRequest.php b/app/Http/Requests/ItemImportRequest.php index 2ea0839c93..a6dc0ad7e5 100644 --- a/app/Http/Requests/ItemImportRequest.php +++ b/app/Http/Requests/ItemImportRequest.php @@ -60,7 +60,7 @@ class ItemImportRequest extends FormRequest $fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER); } $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) - ->setUserId(Auth::id()) + ->setUserId(auth()->id()) ->setUpdating($this->get('import-update')) ->setShouldNotify($this->get('send-welcome')) ->setUsernameFormat('firstname.lastname') diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index b2feb72f79..26d01051b4 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -9,6 +9,7 @@ use App\Models\Setting; use Carbon\Carbon; use Carbon\Exceptions\InvalidFormatException; use Illuminate\Support\Facades\Gate; +use App\Rules\AssetCannotBeCheckedOutToNondeployableStatus; class StoreAssetRequest extends ImageUploadRequest { @@ -25,18 +26,11 @@ class StoreAssetRequest extends ImageUploadRequest public function prepareForValidation(): void { - // Guard against users passing in an array for company_id instead of an integer. - // If the company_id is not an integer then we simply use what was - // provided to be caught by model level validation later. - $idForCurrentUser = is_int($this->company_id) - ? Company::getIdForCurrentUser($this->company_id) - : $this->company_id; - $this->parseLastAuditDate(); $this->merge([ 'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(), - 'company_id' => $idForCurrentUser, + 'company_id' => Company::getIdForCurrentUser($this->company_id), 'assigned_to' => $assigned_to ?? null, ]); } @@ -61,6 +55,7 @@ class StoreAssetRequest extends ImageUploadRequest return array_merge( $modelRules, + ['status_id' => [new AssetCannotBeCheckedOutToNondeployableStatus()]], parent::rules(), ); } diff --git a/app/Http/Requests/StoreLabelSettings.php b/app/Http/Requests/StoreLabelSettings.php new file mode 100644 index 0000000000..a203d2702d --- /dev/null +++ b/app/Http/Requests/StoreLabelSettings.php @@ -0,0 +1,41 @@ +|string> + */ + public function rules(): array + { + return [ + 'labels_per_page' => 'numeric', + 'labels_width' => 'numeric', + 'labels_height' => 'numeric', + 'labels_pmargin_left' => 'numeric|nullable', + 'labels_pmargin_right' => 'numeric|nullable', + 'labels_pmargin_top' => 'numeric|nullable', + 'labels_pmargin_bottom' => 'numeric|nullable', + 'labels_display_bgutter' => 'numeric|nullable', + 'labels_display_sgutter' => 'numeric|nullable', + 'labels_fontsize' => 'numeric|min:5', + 'labels_pagewidth' => 'numeric|nullable', + 'labels_pageheight' => 'numeric|nullable', + 'qr_text' => 'max:31|nullable', + ]; + } +} diff --git a/app/Http/Requests/StoreLdapSettings.php b/app/Http/Requests/StoreLdapSettings.php new file mode 100644 index 0000000000..4197145046 --- /dev/null +++ b/app/Http/Requests/StoreLdapSettings.php @@ -0,0 +1,38 @@ +|string> + */ + public function rules(): array + { + return [ + 'ldap_username_field' => 'not_in:sAMAccountName|required_if:ldap_enabled,1', + 'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1', + 'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1', + 'ldap_server' => 'nullable|required_if:ldap_enabled,1|starts_with:ldap://,ldaps://', + 'ldap_uname' => 'nullable|required_if:ldap_enabled,1', + 'ldap_pword' => 'nullable|required_if:ldap_enabled,1', + 'ldap_basedn' => 'nullable|required_if:ldap_enabled,1', + 'ldap_fname_field' => 'nullable|required_if:ldap_enabled,1', + 'custom_forgot_pass_url' => 'nullable|url', + ]; + } + +} diff --git a/app/Http/Requests/StoreLocalizationSettings.php b/app/Http/Requests/StoreLocalizationSettings.php new file mode 100644 index 0000000000..4cea8826e8 --- /dev/null +++ b/app/Http/Requests/StoreLocalizationSettings.php @@ -0,0 +1,30 @@ +|string> + */ + public function rules(): array + { + return [ + 'default_currency' => 'required', + 'locale' => 'required', + ]; + } +} diff --git a/app/Http/Requests/StoreNotificationSettings.php b/app/Http/Requests/StoreNotificationSettings.php new file mode 100644 index 0000000000..db7e8a0fe2 --- /dev/null +++ b/app/Http/Requests/StoreNotificationSettings.php @@ -0,0 +1,37 @@ +|string> + */ + public function rules(): array + { + return [ + 'alert_email' => 'email_array|nullable', + 'admin_cc_email' => 'email|nullable', + 'alert_threshold' => 'numeric|nullable|gt:0', + 'alert_interval' => 'numeric|nullable|gt:0', + 'audit_warning_days' => 'numeric|nullable|gt:0', + 'due_checkin_days' => 'numeric|nullable|gt:0', + 'audit_interval' => 'numeric|nullable|gt:0', + ]; + } + +} diff --git a/app/Http/Requests/StoreSecuritySettings.php b/app/Http/Requests/StoreSecuritySettings.php new file mode 100644 index 0000000000..42a529aa57 --- /dev/null +++ b/app/Http/Requests/StoreSecuritySettings.php @@ -0,0 +1,35 @@ +|string> + */ + public function rules(): array + { + return [ + 'pwd_secure_min' => 'numeric|required|min:8', + 'custom_forgot_pass_url' => 'url|nullable', + 'privacy_policy_link' => 'nullable|url', + 'login_remote_user_enabled' => 'numeric|nullable', + 'login_common_disabled' => 'numeric|nullable', + 'login_remote_user_custom_logout_url' => 'string|nullable', + 'login_remote_user_header_name' => 'string|nullable', + ]; + } +} diff --git a/app/Http/Transformers/AccessoriesTransformer.php b/app/Http/Transformers/AccessoriesTransformer.php index c85c4e86f4..839576c729 100644 --- a/app/Http/Transformers/AccessoriesTransformer.php +++ b/app/Http/Transformers/AccessoriesTransformer.php @@ -38,9 +38,12 @@ class AccessoriesTransformer 'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost), 'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null, 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, - 'remaining_qty' => (int) $accessory->numRemaining(), + 'remaining_qty' => (int) ($accessory->qty - $accessory->checkouts_count), 'checkouts_count' => $accessory->checkouts_count, - + 'created_by' => ($accessory->adminuser) ? [ + 'id' => (int) $accessory->adminuser->id, + 'name'=> e($accessory->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), @@ -57,7 +60,7 @@ class AccessoriesTransformer $permissions_array['user_can_checkout'] = false; - if ($accessory->numRemaining() > 0) { + if (($accessory->qty - $accessory->checkouts_count) > 0) { $permissions_array['user_can_checkout'] = true; } diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 96d74827d2..d0605c747b 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -176,11 +176,17 @@ class ActionlogsTransformer 'next_audit_date' => ($actionlog->itemType()=='asset') ? Helper::getFormattedDateObject($actionlog->calcNextAuditDate(null, $actionlog->item), 'date'): null, 'days_to_next_audit' => $actionlog->daysUntilNextAudit($settings->audit_interval, $actionlog->item), 'action_type' => $actionlog->present()->actionType(), - 'admin' => ($actionlog->admin) ? [ - 'id' => (int) $actionlog->admin->id, - 'name' => e($actionlog->admin->getFullNameAttribute()), - 'first_name'=> e($actionlog->admin->first_name), - 'last_name'=> e($actionlog->admin->last_name) + 'admin' => ($actionlog->adminuser) ? [ + 'id' => (int) $actionlog->adminuser->id, + 'name' => e($actionlog->adminuser->getFullNameAttribute()), + 'first_name'=> e($actionlog->adminuser->first_name), + 'last_name'=> e($actionlog->adminuser->last_name) + ] : null, + 'created_by' => ($actionlog->adminuser) ? [ + 'id' => (int) $actionlog->adminuser->id, + 'name' => e($actionlog->adminuser->getFullNameAttribute()), + 'first_name'=> e($actionlog->adminuser->first_name), + 'last_name'=> e($actionlog->adminuser->last_name) ] : null, 'target' => ($actionlog->target) ? [ 'id' => (int) $actionlog->target->id, diff --git a/app/Http/Transformers/AssetMaintenancesTransformer.php b/app/Http/Transformers/AssetMaintenancesTransformer.php index 88ac447c25..81b4a9eabb 100644 --- a/app/Http/Transformers/AssetMaintenancesTransformer.php +++ b/app/Http/Transformers/AssetMaintenancesTransformer.php @@ -64,7 +64,14 @@ class AssetMaintenancesTransformer 'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'), 'asset_maintenance_time' => $assetmaintenance->asset_maintenance_time, 'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'date'), - 'user_id' => ($assetmaintenance->admin) ? ['id' => $assetmaintenance->admin->id, 'name'=> e($assetmaintenance->admin->getFullNameAttribute())] : null, + 'user_id' => ($assetmaintenance->adminuser) ? [ + 'id' => $assetmaintenance->adminuser->id, + 'name'=> e($assetmaintenance->adminuser->present()->fullName()) + ] : null, // legacy to not change the shape of the API + 'created_by' => ($assetmaintenance->adminuser) ? [ + 'id' => (int) $assetmaintenance->adminuser->id, + 'name'=> e($assetmaintenance->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'), 'is_warranty'=> $assetmaintenance->is_warranty, diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php index 17693fccf4..d7ee423249 100644 --- a/app/Http/Transformers/AssetsTransformer.php +++ b/app/Http/Transformers/AssetsTransformer.php @@ -80,6 +80,10 @@ class AssetsTransformer 'assigned_to' => $this->transformAssignedTo($asset), 'warranty_months' => ($asset->warranty_months > 0) ? e($asset->warranty_months.' '.trans('admin/hardware/form.months')) : null, 'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null, + 'created_by' => ($asset->adminuser) ? [ + 'id' => (int) $asset->adminuser->id, + 'name'=> e($asset->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'), 'last_audit_date' => Helper::getFormattedDateObject($asset->last_audit_date, 'datetime'), diff --git a/app/Http/Transformers/CategoriesTransformer.php b/app/Http/Transformers/CategoriesTransformer.php index d5e1ceb51b..2dd82b3b70 100644 --- a/app/Http/Transformers/CategoriesTransformer.php +++ b/app/Http/Transformers/CategoriesTransformer.php @@ -62,6 +62,10 @@ class CategoriesTransformer 'consumables_count' => (int) $category->consumables_count, 'components_count' => (int) $category->components_count, 'licenses_count' => (int) $category->licenses_count, + 'created_by' => ($category->adminuser) ? [ + 'id' => (int) $category->adminuser->id, + 'name'=> e($category->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($category->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/CompaniesTransformer.php b/app/Http/Transformers/CompaniesTransformer.php index fe8befc27a..530df32044 100644 --- a/app/Http/Transformers/CompaniesTransformer.php +++ b/app/Http/Transformers/CompaniesTransformer.php @@ -30,14 +30,18 @@ class CompaniesTransformer 'fax' => ($company->fax!='') ? e($company->fax): null, 'email' => ($company->email!='') ? e($company->email): null, 'image' => ($company->image) ? Storage::disk('public')->url('companies/'.e($company->image)) : null, - 'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'), - 'updated_at' => Helper::getFormattedDateObject($company->updated_at, 'datetime'), 'assets_count' => (int) $company->assets_count, 'licenses_count' => (int) $company->licenses_count, 'accessories_count' => (int) $company->accessories_count, 'consumables_count' => (int) $company->consumables_count, 'components_count' => (int) $company->components_count, 'users_count' => (int) $company->users_count, + 'created_by' => ($company->adminuser) ? [ + 'id' => (int) $company->adminuser->id, + 'name'=> e($company->adminuser->present()->fullName()), + ] : null, + 'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($company->updated_at, 'datetime'), ]; $permissions_array['available_actions'] = [ diff --git a/app/Http/Transformers/ComponentsTransformer.php b/app/Http/Transformers/ComponentsTransformer.php index d18870bdc3..70572c9494 100644 --- a/app/Http/Transformers/ComponentsTransformer.php +++ b/app/Http/Transformers/ComponentsTransformer.php @@ -47,6 +47,10 @@ class ComponentsTransformer 'name' => e($component->company->name), ] : null, 'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null, + 'created_by' => ($component->adminuser) ? [ + 'id' => (int) $component->adminuser->id, + 'name'=> e($component->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'), 'user_can_checkout' => ($component->numRemaining() > 0) ? 1 : 0, diff --git a/app/Http/Transformers/ConsumablesTransformer.php b/app/Http/Transformers/ConsumablesTransformer.php index d0ae57eef0..b31e31ac96 100644 --- a/app/Http/Transformers/ConsumablesTransformer.php +++ b/app/Http/Transformers/ConsumablesTransformer.php @@ -40,6 +40,10 @@ class ConsumablesTransformer 'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'), 'qty' => (int) $consumable->qty, 'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null, + 'created_by' => ($consumable->adminuser) ? [ + 'id' => (int) $consumable->adminuser->id, + 'name'=> e($consumable->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/DepreciationReportTransformer.php b/app/Http/Transformers/DepreciationReportTransformer.php index 47d1cbc476..33d9a1f5fd 100644 --- a/app/Http/Transformers/DepreciationReportTransformer.php +++ b/app/Http/Transformers/DepreciationReportTransformer.php @@ -63,15 +63,12 @@ class DepreciationReportTransformer */ if (($asset->model) && ($asset->model->depreciation)) { $depreciated_value = Helper::formatCurrencyOutput($asset->getDepreciatedValue()); - if($asset->model->eol==0 || $asset->model->eol==null ){ - $monthly_depreciation = Helper::formatCurrencyOutput($asset->purchase_cost / $asset->model->depreciation->months); - } - else { - $monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0)); - } + $monthly_depreciation =Helper::formatCurrencyOutput($asset->purchase_cost / $asset->model->depreciation->months); $diff = Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue())); } - + else if($asset->model->eol !== null) { + $monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0)); + } if ($asset->assigned) { $checkout_target = $asset->assigned->name; diff --git a/app/Http/Transformers/DepreciationsTransformer.php b/app/Http/Transformers/DepreciationsTransformer.php index b3dc8c5aae..64d4c88f7e 100644 --- a/app/Http/Transformers/DepreciationsTransformer.php +++ b/app/Http/Transformers/DepreciationsTransformer.php @@ -31,6 +31,10 @@ class DepreciationsTransformer 'assets_count' => $depreciation->assets_count, 'models_count' => $depreciation->models_count, 'licenses_count' => $depreciation->licenses_count, + 'created_by' => ($depreciation->adminuser) ? [ + 'id' => (int) $depreciation->adminuser->id, + 'name'=> e($depreciation->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime') ]; diff --git a/app/Http/Transformers/GroupsTransformer.php b/app/Http/Transformers/GroupsTransformer.php index bf7e2bfd70..03e96d5622 100644 --- a/app/Http/Transformers/GroupsTransformer.php +++ b/app/Http/Transformers/GroupsTransformer.php @@ -26,7 +26,10 @@ class GroupsTransformer 'name' => e($group->name), 'permissions' => json_decode($group->permissions), 'users_count' => (int) $group->users_count, - 'created_by' => ($group->admin) ? e($group->admin->present()->fullName) : null, + 'created_by' => ($group->adminuser) ? [ + 'id' => (int) $group->adminuser->id, + 'name'=> e($group->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index 4fad9b9a68..673ac06b3d 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -61,7 +61,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) && ($license->seats == $license->availCount()->count())) ? true : false, + 'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count > 0)) ? true : false, ]; $array += $permissions_array; diff --git a/app/Http/Transformers/ManufacturersTransformer.php b/app/Http/Transformers/ManufacturersTransformer.php index 9c84fd50fe..e08aaa7436 100644 --- a/app/Http/Transformers/ManufacturersTransformer.php +++ b/app/Http/Transformers/ManufacturersTransformer.php @@ -36,6 +36,10 @@ class ManufacturersTransformer 'licenses_count' => (int) $manufacturer->licenses_count, 'consumables_count' => (int) $manufacturer->consumables_count, 'accessories_count' => (int) $manufacturer->accessories_count, + 'created_by' => ($manufacturer->adminuser) ? [ + 'id' => (int) $manufacturer->adminuser->id, + 'name'=> e($manufacturer->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($manufacturer->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($manufacturer->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($manufacturer->deleted_at, 'datetime'), diff --git a/app/Http/Transformers/PredefinedKitsTransformer.php b/app/Http/Transformers/PredefinedKitsTransformer.php index a5d37e5c72..b5de12fc08 100644 --- a/app/Http/Transformers/PredefinedKitsTransformer.php +++ b/app/Http/Transformers/PredefinedKitsTransformer.php @@ -2,6 +2,7 @@ namespace App\Http\Transformers; +use App\Helpers\Helper; use App\Models\PredefinedKit; use App\Models\SnipeModel; use Illuminate\Support\Facades\Gate; @@ -30,6 +31,12 @@ class PredefinedKitsTransformer $array = [ 'id' => (int) $kit->id, 'name' => e($kit->name), + 'created_by' => ($kit->adminuser) ? [ + 'id' => (int) $kit->adminuser->id, + 'name'=> e($kit->adminuser->present()->fullName()), + ] : null, + 'created_at' => Helper::getFormattedDateObject($kit->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($kit->updated_at, 'datetime'), ]; $permissions_array['available_actions'] = [ diff --git a/app/Http/Transformers/StatuslabelsTransformer.php b/app/Http/Transformers/StatuslabelsTransformer.php index 41dd336068..751edb7016 100644 --- a/app/Http/Transformers/StatuslabelsTransformer.php +++ b/app/Http/Transformers/StatuslabelsTransformer.php @@ -30,6 +30,10 @@ class StatuslabelsTransformer 'default_label' => ($statuslabel->default_label == '1') ? true : false, 'assets_count' => (int) $statuslabel->assets_count, 'notes' => e($statuslabel->notes), + 'created_by' => ($statuslabel->adminuser) ? [ + 'id' => (int) $statuslabel->adminuser->id, + 'name'=> e($statuslabel->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($statuslabel->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($statuslabel->updated_at, 'datetime'), ]; diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index 781a6311fe..1112a04e35 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -177,7 +177,7 @@ class AssetImporter extends ItemImporter $this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created'); // If we have a target to checkout to, lets do so. - //-- user_id is a property of the abstract class Importer, which this class inherits from and it's set by + //-- created_by is a property of the abstract class Importer, which this class inherits from and it's set by //-- the class that needs to use it (command importer or GUI importer inside the project). if (isset($target) && ($target !== false)) { if (!is_null($asset->assigned_to)){ @@ -186,7 +186,7 @@ class AssetImporter extends ItemImporter } } - $asset->fresh()->checkOut($target, $this->user_id, $checkout_date, null, 'Checkout from CSV Importer', $asset->name); + $asset->fresh()->checkOut($target, $this->created_by, $checkout_date, null, 'Checkout from CSV Importer', $asset->name); } return; diff --git a/app/Importer/ComponentImporter.php b/app/Importer/ComponentImporter.php index f72d4cbfd7..9687ec4f17 100644 --- a/app/Importer/ComponentImporter.php +++ b/app/Importer/ComponentImporter.php @@ -58,7 +58,7 @@ class ComponentImporter extends ItemImporter if (isset($this->item['asset_tag']) && ($asset = Asset::where('asset_tag', $this->item['asset_tag'])->first())) { $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'user_id' => $this->user_id, + 'created_by' => $this->created_by, 'created_at' => date('Y-m-d H:i:s'), 'assigned_qty' => 1, // Only assign the first one to the asset 'asset_id' => $asset->id, diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index c2214ef37d..47de5add4c 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -22,7 +22,7 @@ abstract class Importer * @var */ - protected $user_id; + protected $created_by; /** * Are we updating items in the import * @var bool @@ -164,6 +164,7 @@ abstract class Importer $this->log('------------- Action Summary ----------------'); } + Model::reguard(); }); } @@ -395,7 +396,7 @@ abstract class Importer } /** - * Matches a user by user_id if user_name provided is a number + * Matches a user by created_by if user_name provided is a number * @param string $user_name users full name from csv * @return User User Matching ID */ @@ -412,13 +413,13 @@ abstract class Importer /** * Sets the Id of User performing import. * - * @param mixed $user_id the user id + * @param mixed $created_by the user id * * @return self */ - public function setUserId($user_id) + public function setUserId($created_by) { - $this->user_id = $user_id; + $this->created_by = $created_by; return $this; } diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index 29197ca5dc..360618f4f0 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -94,7 +94,7 @@ class ItemImporter extends Importer $this->item['qty'] = $this->findCsvMatch($row, 'quantity'); $this->item['requestable'] = $this->findCsvMatch($row, 'requestable'); - $this->item['user_id'] = $this->user_id; + $this->item['created_by'] = $this->created_by; $this->item['serial'] = $this->findCsvMatch($row, 'serial'); // NO need to call this method if we're running the user import. // TODO: Merge these methods. @@ -301,7 +301,7 @@ class ItemImporter extends Importer $category = new Category(); $category->name = $asset_category; $category->category_type = $item_type; - $category->user_id = $this->user_id; + $category->created_by = $this->created_by; if ($category->save()) { $this->log('Category '.$asset_category.' was created'); @@ -425,7 +425,7 @@ class ItemImporter extends Importer //Otherwise create a manufacturer. $manufacturer = new Manufacturer(); $manufacturer->name = trim($item_manufacturer); - $manufacturer->user_id = $this->user_id; + $manufacturer->created_by = $this->created_by; if ($manufacturer->save()) { $this->log('Manufacturer '.$manufacturer->name.' was created'); @@ -466,7 +466,7 @@ class ItemImporter extends Importer $location->city = ''; $location->state = ''; $location->country = ''; - $location->user_id = $this->user_id; + $location->created_by = $this->created_by; if ($location->save()) { $this->log('Location '.$asset_location.' was created'); @@ -502,7 +502,7 @@ class ItemImporter extends Importer $supplier = new Supplier(); $supplier->name = $item_supplier; - $supplier->user_id = $this->user_id; + $supplier->created_by = $this->created_by; if ($supplier->save()) { $this->log('Supplier '.$item_supplier.' was created'); diff --git a/app/Importer/LicenseImporter.php b/app/Importer/LicenseImporter.php index b7c55cdba6..3f7bb9f85c 100644 --- a/app/Importer/LicenseImporter.php +++ b/app/Importer/LicenseImporter.php @@ -103,13 +103,13 @@ class LicenseImporter extends ItemImporter if ($checkout_target) { $targetLicense->assigned_to = $checkout_target->id; - $targetLicense->user_id = Auth::id(); + $targetLicense->created_by = auth()->id(); if ($asset) { $targetLicense->asset_id = $asset->id; } $targetLicense->save(); } elseif ($asset) { - $targetLicense->user_id = Auth::id(); + $targetLicense->created_by = auth()->id(); $targetLicense->asset_id = $asset->id; $targetLicense->save(); } diff --git a/app/Importer/LocationImporter.php b/app/Importer/LocationImporter.php index e344b6beaf..b3ef59d248 100644 --- a/app/Importer/LocationImporter.php +++ b/app/Importer/LocationImporter.php @@ -65,7 +65,7 @@ class LocationImporter extends ItemImporter $this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou')); $this->item['manager'] = trim($this->findCsvMatch($row, 'manager')); $this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username')); - $this->item['user_id'] = auth()->id(); + $this->item['created_by'] = auth()->id(); if ($this->findCsvMatch($row, 'parent_location')) { $this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location'))); diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index 4a8d76b68e..036bf15c9a 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -165,7 +165,7 @@ class UserImporter extends ItemImporter $department = new department(); $department->name = $department_name; - $department->user_id = $this->user_id; + $department->created_by = $this->created_by; if ($department->save()) { $this->log('department ' . $department_name . ' was created'); diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index 5968632fdc..eb6b738094 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -11,6 +11,7 @@ use App\Models\Consumable; use App\Models\LicenseSeat; use App\Models\Recipients\AdminRecipient; use App\Models\Setting; +use App\Models\User; use App\Notifications\CheckinAccessoryNotification; use App\Notifications\CheckinAssetNotification; use App\Notifications\CheckinLicenseSeatNotification; @@ -43,30 +44,31 @@ class CheckoutableListener * Make a checkout acceptance and attach it in the notification */ $acceptance = $this->getCheckoutAcceptance($event); + $notifiables = $this->getNotifiables($event); + // Send email notifications try { - if (! $event->checkedOutTo->locale) { - Notification::locale(Setting::getSettings()->locale)->send( - $this->getNotifiables($event), - $this->getCheckoutNotification($event, $acceptance) - ); - } else { - Notification::send( - $this->getNotifiables($event), - $this->getCheckoutNotification($event, $acceptance) - ); + foreach ($notifiables as $notifiable) { + if ($notifiable instanceof User && $notifiable->email != '') { + if (! $event->checkedOutTo->locale){ + Notification::locale(Setting::getSettings()->locale)->send($notifiable, $this->getCheckoutNotification($event, $acceptance)); + } + else { + Notification::send($notifiable, $this->getCheckoutNotification($event, $acceptance)); + } + } } + // Send Webhook notification if ($this->shouldSendWebhookNotification()) { - - //slack doesn't include the url in its messaging format so this is needed to hit the endpoint - - if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') { - - - Notification::route('slack', Setting::getSettings()->webhook_endpoint) - ->notify($this->getCheckoutNotification($event)); - } + // Slack doesn't include the URL in its messaging format, so this is needed to hit the endpoint + if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general') { + Notification::route('slack', Setting::getSettings()->webhook_endpoint) + ->notify($this->getCheckoutNotification($event, $acceptance)); + } else { + Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint) + ->notify($this->getCheckoutNotification($event, $acceptance)); + } } } catch (ClientException $e) { Log::debug("Exception caught during checkout notification: " . $e->getMessage()); @@ -75,6 +77,7 @@ class CheckoutableListener } } + /** * Notify the user and post to webhook about the checked in checkoutable */ @@ -101,25 +104,28 @@ class CheckoutableListener } } + $notifiables = $this->getNotifiables($event); + // Send email notifications try { - // Use default locale - if (! $event->checkedOutTo->locale) { - Notification::locale(Setting::getSettings()->locale)->send( - $this->getNotifiables($event), - $this->getCheckinNotification($event) - ); - } else { - Notification::send( - $this->getNotifiables($event), - $this->getCheckinNotification($event) - ); + foreach ($notifiables as $notifiable) { + if ($notifiable instanceof User && $notifiable->email != '') { + if (! $event->checkedOutTo->locale){ + Notification::locale(Setting::getSettings()->locale)->send($notifiable, $this->getCheckoutNotification($event, $acceptance)); + } + else { + Notification::send($notifiable, $this->getCheckinNotification($event)); + } + } } - //slack doesn't include the url in its messaging format so this is needed to hit the endpoint - if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') { - - if ($this->shouldSendWebhookNotification()) { + // Send Webhook notification + if ($this->shouldSendWebhookNotification()) { + // Slack doesn't include the URL in its messaging format, so this is needed to hit the endpoint + if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general') { Notification::route('slack', Setting::getSettings()->webhook_endpoint) ->notify($this->getCheckinNotification($event)); + } else { + Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint) + ->notify($this->getCheckinNotification($event)); } } diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php index b44fcdfcb4..6dbeb7312c 100644 --- a/app/Listeners/LogListener.php +++ b/app/Listeners/LogListener.php @@ -111,7 +111,7 @@ class LogListener $logaction->target_type = User::class; $logaction->action_type = 'merged'; $logaction->note = trans('general.merged_log_this_user_from', $to_from_array); - $logaction->user_id = $event->admin->id ?? null; + $logaction->created_by = $event->admin->id ?? null; $logaction->save(); // Add a record to the users being merged TO @@ -122,7 +122,7 @@ class LogListener $logaction->item_type = User::class; $logaction->action_type = 'merged'; $logaction->note = trans('general.merged_log_this_user_into', $to_from_array); - $logaction->user_id = $event->admin->id ?? null; + $logaction->created_by = $event->admin->id ?? null; $logaction->save(); diff --git a/app/Livewire/OauthClients.php b/app/Livewire/OauthClients.php index fda91260c8..017e789060 100644 --- a/app/Livewire/OauthClients.php +++ b/app/Livewire/OauthClients.php @@ -47,10 +47,10 @@ class OauthClients extends Component { // test for safety // ->delete must be of type Client - thus the model binding - if ($clientId->user_id == auth()->id()) { + if ($clientId->created_by == auth()->id()) { app(ClientRepository::class)->delete($clientId); } else { - Log::warning('User ' . auth()->id() . ' attempted to delete client ' . $clientId->id . ' which belongs to user ' . $clientId->user_id); + Log::warning('User ' . auth()->id() . ' attempted to delete client ' . $clientId->id . ' which belongs to user ' . $clientId->created_by); $this->authorizationError = 'You are not authorized to delete this client.'; } } @@ -58,10 +58,10 @@ class OauthClients extends Component public function deleteToken($tokenId): void { $token = app(TokenRepository::class)->find($tokenId); - if ($token->user_id == auth()->id()) { + if ($token->created_by == auth()->id()) { app(TokenRepository::class)->revokeAccessToken($tokenId); } else { - Log::warning('User ' . auth()->id() . ' attempted to delete token ' . $tokenId . ' which belongs to user ' . $token->user_id); + Log::warning('User ' . auth()->id() . ' attempted to delete token ' . $tokenId . ' which belongs to user ' . $token->created_by); $this->authorizationError = 'You are not authorized to delete this token.'; } } @@ -84,12 +84,12 @@ class OauthClients extends Component ]); $client = app(ClientRepository::class)->find($editClientId->id); - if ($client->user_id == auth()->id()) { + if ($client->created_by == auth()->id()) { $client->name = $this->editName; $client->redirect = $this->editRedirect; $client->save(); } else { - Log::warning('User ' . auth()->id() . ' attempted to edit client ' . $editClientId->id . ' which belongs to user ' . $client->user_id); + Log::warning('User ' . auth()->id() . ' attempted to edit client ' . $editClientId->id . ' which belongs to user ' . $client->created_by); $this->authorizationError = 'You are not authorized to edit this client.'; } diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index c1366f67e6..fc1bb36ab4 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -62,7 +62,7 @@ class Accessory extends SnipeModel 'category_id' => 'required|integer|exists:categories,id', 'company_id' => 'integer|nullable', 'min_amt' => 'integer|min:0|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0', + 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', 'purchase_date' => 'date_format:Y-m-d|nullable', ]; @@ -259,6 +259,18 @@ class Accessory extends SnipeModel ->with('assignedTo'); } + /** + * Establishes the accessory -> admin user relationship + * + * @author A. Gianotto + * @since [v7.0.13] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Checks whether or not the accessory has users * @@ -410,6 +422,16 @@ class Accessory extends SnipeModel * ----------------------------------------------- **/ + + /** + * Query builder scope to order on created_by name + * + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'accessories.created_by', '=', 'admin_sort.id')->select('accessories.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + /** * Query builder scope to order on company * diff --git a/app/Models/AccessoryCheckout.php b/app/Models/AccessoryCheckout.php index 7f42b354e1..bdfbf11d9d 100755 --- a/app/Models/AccessoryCheckout.php +++ b/app/Models/AccessoryCheckout.php @@ -22,7 +22,7 @@ class AccessoryCheckout extends Model { use Searchable; - protected $fillable = ['user_id', 'accessory_id', 'assigned_to', 'assigned_type', 'note']; + protected $fillable = ['created_by', 'accessory_id', 'assigned_to', 'assigned_type', 'note']; protected $table = 'accessories_checkout'; /** diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 7f3b46e46c..0831352b87 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -21,7 +21,7 @@ class Actionlog extends SnipeModel // This is to manually set the source (via setActionSource()) for determineActionSource() protected ?string $source = null; - protected $with = ['admin']; + protected $with = ['adminuser']; protected $presenter = \App\Presenters\ActionlogPresenter::class; use SoftDeletes; @@ -32,7 +32,7 @@ class Actionlog extends SnipeModel protected $fillable = [ 'created_at', 'item_type', - 'user_id', + 'created_by', 'item_id', 'action_type', 'note', @@ -52,7 +52,7 @@ class Actionlog extends SnipeModel 'action_type', 'note', 'log_meta', - 'user_id', + 'created_by', 'remote_ip', 'user_agent', 'action_source' @@ -65,7 +65,7 @@ class Actionlog extends SnipeModel */ protected $searchableRelations = [ 'company' => ['name'], - 'admin' => ['first_name','last_name','username', 'email'], + 'adminuser' => ['first_name','last_name','username', 'email'], 'user' => ['first_name','last_name','username', 'email'], 'assets' => ['asset_tag','name'], ]; @@ -198,9 +198,9 @@ class Actionlog extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(User::class, 'user_id') + return $this->belongsTo(User::class, 'created_by') ->withTrashed(); } @@ -374,8 +374,8 @@ class Actionlog extends SnipeModel $this->source = $source; } - public function scopeOrderAdmin($query, $order) + public function scopeOrderByCreatedBy($query, $order) { - return $query->leftJoin('users as admin_sort', 'action_logs.user_id', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + return $query->leftJoin('users as admin_sort', 'action_logs.created_by', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } } diff --git a/app/Models/Asset.php b/app/Models/Asset.php index aff06b6689..ce8b870eb2 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -30,7 +30,7 @@ class Asset extends Depreciable { protected $presenter = AssetPresenter::class; - protected $with = ['model', 'admin']; + protected $with = ['model', 'adminuser']; use CompanyableTrait; use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait; @@ -43,16 +43,16 @@ class Asset extends Depreciable /** * Run after the checkout acceptance was declined by the user - * + * * @param User $acceptedBy * @param string $signature - */ + */ public function declinedCheckout(User $declinedBy, $signature) { $this->assigned_to = null; $this->assigned_type = null; - $this->accepted = null; - $this->save(); + $this->accepted = null; + $this->save(); } /** @@ -108,12 +108,11 @@ class Asset extends Depreciable 'expected_checkin' => ['nullable', 'date'], 'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'], 'next_audit_date' => ['nullable', 'date'], - //'after:last_audit_date'], 'location_id' => ['nullable', 'exists:locations,id'], 'rtd_location_id' => ['nullable', 'exists:locations,id'], 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], 'serial' => ['nullable', 'unique_undeleted:assets,serial'], - 'purchase_cost' => ['nullable', 'numeric', 'gte:0'], + 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'], 'supplier_id' => ['nullable', 'exists:suppliers,id'], 'asset_eol_date' => ['nullable', 'date'], 'eol_explicit' => ['nullable', 'boolean'], @@ -369,7 +368,7 @@ class Asset extends Depreciable if ($this->save()) { if (is_int($admin)) { $checkedOutBy = User::findOrFail($admin); - } elseif (get_class($admin) === \App\Models\User::class) { + } elseif ($admin && get_class($admin) === \App\Models\User::class) { $checkedOutBy = $admin; } else { $checkedOutBy = auth()->user(); @@ -710,15 +709,15 @@ class Asset extends Depreciable } /** - * Get action logs history for this asset + * Get user who created the item * * @author [A. Gianotto] [] * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } @@ -931,9 +930,20 @@ class Asset extends Depreciable * */ public function checkInvalidNextAuditDate() { - if (($this->last_audit_date) && ($this->next_audit_date) && ($this->last_audit_date > $this->next_audit_date)) { + + // Deliberately parse the dates as Y-m-d (without H:i:s) to compare them + if ($this->last_audit_date) { + $last = Carbon::parse($this->last_audit_date)->format('Y-m-d'); + } + + if ($this->next_audit_date) { + $next = Carbon::parse($this->next_audit_date)->format('Y-m-d'); + } + + if ((isset($last) && (isset($next))) && ($last > $next)) { return true; } + return false; } @@ -1695,7 +1705,7 @@ class Asset extends Depreciable }); }); } - + /** * THIS CLUNKY BIT IS VERY IMPORTANT @@ -1716,7 +1726,7 @@ class Asset extends Depreciable * assets.location would fail, as that field doesn't exist -- plus we're already searching * against those relationships earlier in this method. * - * - snipe + * - snipe * */ @@ -1761,6 +1771,20 @@ class Asset extends Depreciable } + /** + * Query builder scope to order on created_by name + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'assets.created_by', '=', 'admin_sort.id')->select('assets.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + + /** * Query builder scope to order on assigned user * diff --git a/app/Models/AssetMaintenance.php b/app/Models/AssetMaintenance.php index 5f66783cbb..246220f5c7 100644 --- a/app/Models/AssetMaintenance.php +++ b/app/Models/AssetMaintenance.php @@ -174,9 +174,9 @@ class AssetMaintenance extends Model implements ICompanyableChild * @author A. Gianotto * @version v3.0 */ - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id') + return $this->belongsTo(\App\Models\User::class, 'created_by') ->withTrashed(); } @@ -207,20 +207,6 @@ class AssetMaintenance extends Model implements ICompanyableChild } - /** - * Query builder scope to order on admin user - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ - public function scopeOrderAdmin($query, $order) - { - return $query->leftJoin('users', 'asset_maintenances.user_id', '=', 'users.id') - ->orderBy('users.first_name', $order) - ->orderBy('users.last_name', $order); - } /** * Query builder scope to order on asset tag @@ -278,4 +264,12 @@ class AssetMaintenance extends Model implements ICompanyableChild ->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id') ->orderBy('maintained_asset_status.name', $order); } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'asset_maintenances.created_by', '=', 'admin_sort.id')->select('asset_maintenances.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index e9b859e128..0c8f8e7b3c 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -36,7 +36,6 @@ class AssetModel extends SnipeModel protected $injectUniqueIdentifier = true; use ValidatingTrait; protected $table = 'models'; - protected $hidden = ['user_id', 'deleted_at']; protected $presenter = AssetModelPresenter::class; // Declare the rules for the model validation @@ -69,7 +68,6 @@ class AssetModel extends SnipeModel 'model_number', 'name', 'notes', - 'user_id', ]; use Searchable; @@ -226,6 +224,18 @@ class AssetModel extends SnipeModel ->orderBy('created_at', 'desc'); } + /** + * Get user who created the item + * + * @author [A. Gianotto] [] + * @since [v1.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * ----------------------------------------------- diff --git a/app/Models/Category.php b/app/Models/Category.php index f21038bab0..5965404f59 100755 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -29,17 +29,17 @@ class Category extends SnipeModel use SoftDeletes; protected $table = 'categories'; - protected $hidden = ['user_id', 'deleted_at']; + protected $hidden = ['created_by', 'deleted_at']; protected $casts = [ - 'user_id' => 'integer', + 'created_by' => 'integer', ]; /** * Category validation rules */ public $rules = [ - 'user_id' => 'numeric|nullable', + 'created_by' => 'numeric|nullable', 'name' => 'required|min:1|max:255|two_column_unique_undeleted:category_type', 'require_acceptance' => 'boolean', 'use_default_eula' => 'boolean', @@ -70,7 +70,7 @@ class Category extends SnipeModel 'name', 'require_acceptance', 'use_default_eula', - 'user_id', + 'created_by', ]; use Searchable; @@ -228,6 +228,11 @@ class Category extends SnipeModel return $this->hasMany(\App\Models\AssetModel::class, 'category_id'); } + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Checks for a category-specific EULA, and if that doesn't exist, * checks for a settings level EULA @@ -286,4 +291,9 @@ class Category extends SnipeModel { return $query->where('require_acceptance', '=', true); } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'categories.created_by', '=', 'admin_sort.id')->select('categories.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/CheckoutRequest.php b/app/Models/CheckoutRequest.php index b717a332aa..d6a85f2972 100644 --- a/app/Models/CheckoutRequest.php +++ b/app/Models/CheckoutRequest.php @@ -13,7 +13,7 @@ class CheckoutRequest extends Model public function user() { - return $this->belongsTo(User::class); + return $this->belongsTo(User::class, 'user_id', 'id'); } public function requestingUser() diff --git a/app/Models/Company.php b/app/Models/Company.php index 657b34390b..171d559542 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -67,6 +67,7 @@ final class Company extends SnipeModel 'phone', 'fax', 'email', + 'created_by' ]; private static function isFullMultipleCompanySupportEnabled() @@ -186,12 +187,15 @@ final class Company extends SnipeModel */ public function isDeletable() { + return Gate::allows('delete', $this) - && ($this->assets()->count() === 0) - && ($this->accessories()->count() === 0) - && ($this->consumables()->count() === 0) - && ($this->components()->count() === 0) - && ($this->users()->count() === 0); + && (($this->assets_count ?? $this->assets()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->licenses_count ?? $this->licenses()->count()) === 0) + && (($this->components_count ?? $this->components()->count()) === 0) + && (($this->consumables_count ?? $this->consumables()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->users_count ?? $this->users()->count()) === 0); } /** @@ -294,6 +298,12 @@ final class Company extends SnipeModel } + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + /** * I legit do not know what this method does, but we can't remove it (yet). * @@ -329,4 +339,13 @@ final class Company extends SnipeModel } } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'companies.created_by', '=', 'admin_sort.id')->select('companies.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Component.php b/app/Models/Component.php index 536e06d0af..761c76f097 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -30,14 +30,14 @@ class Component extends SnipeModel * Category validation rules */ public $rules = [ - 'name' => 'required|min:3|max:255', + 'name' => 'required|min:3|max:191', 'qty' => 'required|integer|min:1', 'category_id' => 'required|integer|exists:categories,id', 'supplier_id' => 'nullable|integer|exists:suppliers,id', 'company_id' => 'integer|nullable|exists:companies,id', 'min_amt' => 'integer|min:0|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0', + 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', ]; /** @@ -130,7 +130,7 @@ class Component extends SnipeModel */ public function assets() { - return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'user_id', 'note'); + return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'created_by', 'note'); } /** @@ -142,9 +142,9 @@ class Component extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } /** @@ -310,4 +310,9 @@ class Component extends SnipeModel { return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order); } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'components.created_by', '=', 'admin_sort.id')->select('components.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 3b33035b1e..30161e8429 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -50,7 +50,7 @@ class Consumable extends SnipeModel 'category_id' => 'required|integer', 'company_id' => 'integer|nullable', 'min_amt' => 'integer|min:0|max:99999|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0', + 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', 'purchase_date' => 'date_format:Y-m-d|nullable', ]; @@ -154,9 +154,9 @@ class Consumable extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(User::class, 'user_id'); + return $this->belongsTo(User::class, 'created_by'); } /** @@ -256,7 +256,7 @@ class Consumable extends SnipeModel */ public function users() : Relation { - return $this->belongsToMany(User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps(); + return $this->belongsToMany(User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('created_by')->withTrashed()->withTimestamps(); } /** @@ -451,4 +451,9 @@ class Consumable extends SnipeModel { return $query->leftJoin('suppliers', 'consumables.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order); } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as users_sort', 'consumables.created_by', '=', 'users_sort.id')->select('consumables.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order); + } } diff --git a/app/Models/ConsumableAssignment.php b/app/Models/ConsumableAssignment.php index db0cfa4bd6..4c9a19703e 100644 --- a/app/Models/ConsumableAssignment.php +++ b/app/Models/ConsumableAssignment.php @@ -26,8 +26,8 @@ class ConsumableAssignment extends Model return $this->belongsTo(\App\Models\User::class, 'assigned_to'); } - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } } diff --git a/app/Models/Department.php b/app/Models/Department.php index 62755d2aa0..855cb25f64 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -42,7 +42,7 @@ class Department extends SnipeModel * @var array */ protected $fillable = [ - 'user_id', + 'created_by', 'name', 'phone', 'fax', diff --git a/app/Models/Depreciation.php b/app/Models/Depreciation.php index 7aceddf7c4..11ee82c16a 100755 --- a/app/Models/Depreciation.php +++ b/app/Models/Depreciation.php @@ -88,4 +88,27 @@ class Depreciation extends SnipeModel return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'depreciation_id', 'model_id'); } + /** + * Get the user that created the depreciation + * + * @author A. Gianotto + * @since [v7.0.13] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + + /** + * ----------------------------------------------- + * BEGIN QUERY SCOPES + * ----------------------------------------------- + **/ + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'depreciations.created_by', '=', 'admin_sort.id')->select('depreciations.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Group.php b/app/Models/Group.php index c6e6e56039..7278152df9 100755 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -65,7 +65,7 @@ class Group extends SnipeModel * @since [v6.3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { return $this->belongsTo(\App\Models\User::class, 'created_by'); } @@ -81,4 +81,16 @@ class Group extends SnipeModel { return json_decode($this->permissions, true); } + + /** + * ----------------------------------------------- + * BEGIN QUERY SCOPES + * ----------------------------------------------- + **/ + + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'permission_groups.created_by', '=', 'admin_sort.id')->select('permission_groups.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Import.php b/app/Models/Import.php index 81728c8a5e..052612a197 100644 --- a/app/Models/Import.php +++ b/app/Models/Import.php @@ -2,10 +2,13 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Import extends Model { + use HasFactory; + protected $casts = [ 'header_row' => 'array', 'first_row' => 'array', diff --git a/app/Models/Ldap.php b/app/Models/Ldap.php index ecce46d82a..f71f926a93 100644 --- a/app/Models/Ldap.php +++ b/app/Models/Ldap.php @@ -283,9 +283,10 @@ class Ldap extends Model * @param $base_dn * @param $count * @param $filter + * @param $attributes * @return array|bool */ - public static function findLdapUsers($base_dn = null, $count = -1, $filter = null) + public static function findLdapUsers($base_dn = null, $count = -1, $filter = null, $attributes = []) { $ldapconn = self::connectToLdap(); self::bindAdminToLdap($ldapconn); @@ -319,7 +320,7 @@ class Ldap extends Model //if($count == -1) { //count is -1 means we have to employ paging to query the entire directory $ldap_controls = [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'iscritical' => false, 'value' => ['size'=> $count == -1||$count>$page_size ? $page_size : $count, 'cookie' => $cookie]]]; //} - $search_results = ldap_search($ldapconn, $base_dn, $filter, [], 0, /* $page_size */ -1, -1, LDAP_DEREF_NEVER, $ldap_controls); // TODO - I hate the @, and I hate that we get a full page even if we ask for 10 records. Can we use an ldap_control? + $search_results = ldap_search($ldapconn, $base_dn, $filter, $attributes, 0, /* $page_size */ -1, -1, LDAP_DEREF_NEVER, $ldap_controls); // TODO - I hate the @, and I hate that we get a full page even if we ask for 10 records. Can we use an ldap_control? Log::debug("LDAP search executed successfully."); if (! $search_results) { return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn)); // TODO this is never called in any routed context - only from the Artisan command. So this redirect will never work. @@ -340,7 +341,7 @@ class Ldap extends Model $cookie = ''; } // Empty cookie means last page - + // Get results from page $results = ldap_get_entries($ldapconn, $search_results); if (! $results) { diff --git a/app/Models/License.php b/app/Models/License.php index 554929c0ac..4923072f07 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -81,8 +81,7 @@ class License extends Depreciable 'serial', 'supplier_id', 'termination_date', - 'free_seat_count', - 'user_id', + 'created_by', 'min_amt', ]; @@ -184,7 +183,7 @@ class License extends Depreciable $logAction = new Actionlog; $logAction->item_type = self::class; $logAction->item_id = $license->id; - $logAction->user_id = Auth::id() ?: 1; // We don't have an id while running the importer from CLI. + $logAction->created_by = auth()->id() ?: 1; // We don't have an id while running the importer from CLI. $logAction->note = "deleted ${change} seats"; $logAction->target_id = null; $logAction->logaction('delete seats'); @@ -196,7 +195,7 @@ class License extends Depreciable $licenseInsert = []; for ($i = $oldSeats; $i < $newSeats; $i++) { $licenseInsert[] = [ - 'user_id' => Auth::id(), + 'created_by' => auth()->id(), 'license_id' => $license->id, 'created_at' => now(), 'updated_at' => now() @@ -216,7 +215,7 @@ class License extends Depreciable $logAction = new Actionlog(); $logAction->item_type = self::class; $logAction->item_id = $license->id; - $logAction->user_id = Auth::id() ?: 1; // Importer. + $logAction->created_by = auth()->id() ?: 1; // Importer. $logAction->note = "added ${change} seats"; $logAction->target_id = null; $logAction->logaction('add seats'); @@ -434,7 +433,7 @@ class License extends Depreciable */ public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } /** @@ -739,14 +738,9 @@ class License extends Depreciable /** * Query builder scope to order on the user that created it - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder */ - public function scopeOrderCreatedBy($query, $order) + public function scopeOrderByCreatedBy($query, $order) { - return $query->leftJoin('users as users_sort', 'licenses.user_id', '=', 'users_sort.id')->select('licenses.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order); + return $query->leftJoin('users as admin_sort', 'licenses.created_by', '=', 'admin_sort.id')->select('licenses.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } } \ No newline at end of file diff --git a/app/Models/Location.php b/app/Models/Location.php index e6c310979b..014db3053e 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -42,7 +42,7 @@ class Location extends SnipeModel ]; /** - * Whether the model should inject it's identifier to the unique + * Whether the model should inject its identifier to the unique * validation rules before attempting validation. If this property * is not set in the model it will default to true. * diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php index ae5d554882..72a7a7f262 100644 --- a/app/Models/Loggable.php +++ b/app/Models/Loggable.php @@ -37,7 +37,7 @@ trait Loggable $log = new Actionlog; $log = $this->determineLogItemType($log); if (auth()->user()) { - $log->user_id = auth()->id(); + $log->created_by = auth()->id(); } if (! isset($target)) { @@ -117,7 +117,6 @@ trait Loggable */ public function logCheckin($target, $note, $action_date = null, $originalValues = []) { - $settings = Setting::getSettings(); $log = new Actionlog; if($target != null){ @@ -149,7 +148,7 @@ trait Loggable } if (auth()->user()) { - $log->user_id = auth()->id(); + $log->created_by = auth()->id(); } $changed = []; @@ -171,39 +170,6 @@ trait Loggable $log->logaction('checkin from'); -// $params = [ -// 'target' => $target, -// 'item' => $log->item, -// 'admin' => $log->user, -// 'note' => $note, -// 'target_type' => $log->target_type, -// 'settings' => $settings, -// ]; -// -// -// $checkinClass = null; -// -// if (method_exists($target, 'notify')) { -// try { -// $target->notify(new static::$checkinClass($params)); -// } catch (\Exception $e) { -// Log::debug($e); -// } -// -// } -// -// // Send to the admin, if settings dictate -// $recipient = new \App\Models\Recipients\AdminRecipient(); -// -// if (($settings->admin_cc_email!='') && (static::$checkinClass!='')) { -// try { -// $recipient->notify(new static::$checkinClass($params)); -// } catch (\Exception $e) { -// Log::debug($e); -// } -// -// } - return $log; } @@ -225,14 +191,14 @@ trait Loggable } $log->location_id = ($location_id) ? $location_id : null; $log->note = $note; - $log->user_id = auth()->id(); + $log->created_by = auth()->id(); $log->filename = $filename; $log->logaction('audit'); $params = [ 'item' => $log->item, 'filename' => $log->filename, - 'admin' => $log->admin, + 'admin' => $log->adminuser, 'location' => ($location) ? $location->name : '', 'note' => $note, ]; @@ -248,9 +214,9 @@ trait Loggable */ public function logCreate($note = null) { - $user_id = -1; + $created_by = -1; if (auth()->user()) { - $user_id = auth()->id(); + $created_by = auth()->id(); } $log = new Actionlog; if (static::class == LicenseSeat::class) { @@ -262,7 +228,7 @@ trait Loggable } $log->location_id = null; $log->note = $note; - $log->user_id = $user_id; + $log->created_by = $created_by; $log->logaction('create'); $log->save(); @@ -284,7 +250,7 @@ trait Loggable $log->item_type = static::class; $log->item_id = $this->id; } - $log->user_id = auth()->id(); + $log->created_by = auth()->id(); $log->note = $note; $log->target_id = null; $log->created_at = date('Y-m-d H:i:s'); diff --git a/app/Models/Manufacturer.php b/app/Models/Manufacturer.php index 85907f7ddb..6e72b3a2bb 100755 --- a/app/Models/Manufacturer.php +++ b/app/Models/Manufacturer.php @@ -74,10 +74,10 @@ class Manufacturer extends SnipeModel public function isDeletable() { return Gate::allows('delete', $this) - && ($this->assets()->count() === 0) - && ($this->licenses()->count() === 0) - && ($this->consumables()->count() === 0) - && ($this->accessories()->count() === 0) + && (($this->assets_count ?? $this->assets()->count()) === 0) + && (($this->licenses_count ?? $this->licenses()->count()) === 0) + && (($this->consumables_count ?? $this->consumables()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) && ($this->deleted_at == ''); } @@ -105,4 +105,19 @@ class Manufacturer extends SnipeModel { return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id'); } + + + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'manufacturers.created_by', '=', 'admin_sort.id')->select('manufacturers.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/PredefinedKit.php b/app/Models/PredefinedKit.php index 1bf6cb098b..36790a1fc7 100644 --- a/app/Models/PredefinedKit.php +++ b/app/Models/PredefinedKit.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Models\Traits\Searchable; use App\Presenters\Presentable; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Validation\Rule; use Watson\Validating\ValidatingTrait; @@ -16,6 +17,7 @@ use Watson\Validating\ValidatingTrait; class PredefinedKit extends SnipeModel { protected $presenter = \App\Presenters\PredefinedKitPresenter::class; + use HasFactory; use Presentable; protected $table = 'kits'; @@ -133,6 +135,13 @@ class PredefinedKit extends SnipeModel */ protected $searchableRelations = []; + + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + /** * Establishes the kits -> models relationship * @return \Illuminate\Database\Eloquent\Relations\Relation @@ -179,4 +188,9 @@ class PredefinedKit extends SnipeModel * BEGIN QUERY SCOPES * ----------------------------------------------- **/ + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'kits.created_by', '=', 'admin_sort.id')->select('kits.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Requestable.php b/app/Models/Requestable.php index bf5c9c427b..4dead82bb3 100644 --- a/app/Models/Requestable.php +++ b/app/Models/Requestable.php @@ -29,19 +29,19 @@ trait Requestable public function request($qty = 1) { $this->requests()->save( - new CheckoutRequest(['user_id' => Auth::id(), 'qty' => $qty]) + new CheckoutRequest(['user_id' => auth()->id(), 'qty' => $qty]) ); } public function deleteRequest() { - $this->requests()->where('user_id', Auth::id())->delete(); + $this->requests()->where('user_id', auth()->id())->delete(); } public function cancelRequest($user_id = null) { if (!$user_id){ - $user_id = Auth::id(); + $user_id = auth()->id(); } $this->requests()->where('user_id', $user_id)->update(['canceled_at' => \Carbon\Carbon::now()]); diff --git a/app/Models/Setting.php b/app/Models/Setting.php index d775be81c5..6f585b95f8 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -51,36 +51,7 @@ class Setting extends Model */ protected $rules = [ 'brand' => 'required|min:1|numeric', - 'qr_text' => 'max:31|nullable', - 'alert_email' => 'email_array|nullable', - 'admin_cc_email' => 'email|nullable', - 'default_currency' => 'required', - 'locale' => 'required', - 'labels_per_page' => 'numeric', - 'labels_width' => 'numeric', - 'labels_height' => 'numeric', - 'labels_pmargin_left' => 'numeric|nullable', - 'labels_pmargin_right' => 'numeric|nullable', - 'labels_pmargin_top' => 'numeric|nullable', - 'labels_pmargin_bottom' => 'numeric|nullable', - 'labels_display_bgutter' => 'numeric|nullable', - 'labels_display_sgutter' => 'numeric|nullable', - 'labels_fontsize' => 'numeric|min:5', - 'labels_pagewidth' => 'numeric|nullable', - 'labels_pageheight' => 'numeric|nullable', - 'login_remote_user_enabled' => 'numeric|nullable', - 'login_common_disabled' => 'numeric|nullable', - 'login_remote_user_custom_logout_url' => 'string|nullable', - 'login_remote_user_header_name' => 'string|nullable', 'thumbnail_max_h' => 'numeric|max:500|min:25', - 'pwd_secure_min' => 'numeric|required|min:8', - 'alert_threshold' => 'numeric|nullable', - 'alert_interval' => 'numeric|nullable', - 'audit_warning_days' => 'numeric|nullable', - 'due_checkin_days' => 'numeric|nullable', - 'audit_interval' => 'numeric|nullable', - 'custom_forgot_pass_url' => 'url|nullable', - 'privacy_policy_link' => 'nullable|url', 'google_client_id' => 'nullable|ends_with:apps.googleusercontent.com' ]; diff --git a/app/Models/Statuslabel.php b/app/Models/Statuslabel.php index 0f8a0b6075..c1bcc3042d 100755 --- a/app/Models/Statuslabel.php +++ b/app/Models/Statuslabel.php @@ -64,6 +64,11 @@ class Statuslabel extends SnipeModel return $this->hasMany(\App\Models\Asset::class, 'status_id'); } + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Gets the status label type * @@ -161,4 +166,9 @@ class Statuslabel extends SnipeModel return $statustype; } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'status_labels.created_by', '=', 'admin_sort.id')->select('status_labels.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Notifications/CheckoutAssetNotification.php b/app/Notifications/CheckoutAssetNotification.php index 5ebde7e4f7..b14796fb8c 100644 --- a/app/Notifications/CheckoutAssetNotification.php +++ b/app/Notifications/CheckoutAssetNotification.php @@ -192,10 +192,9 @@ public function toGoogleChat() * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail() - { + { $this->item->load('assetstatus'); $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; - $fields = []; // Check if the item has custom fields associated with it diff --git a/app/Observers/AccessoryObserver.php b/app/Observers/AccessoryObserver.php index 661d00b4c2..0f8b2492cd 100644 --- a/app/Observers/AccessoryObserver.php +++ b/app/Observers/AccessoryObserver.php @@ -20,7 +20,7 @@ class AccessoryObserver $logAction->item_type = Accessory::class; $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('update'); } @@ -37,7 +37,7 @@ class AccessoryObserver $logAction->item_type = Accessory::class; $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($accessory->imported) { $logAction->setActionSource('importer'); } @@ -56,7 +56,7 @@ class AccessoryObserver $logAction->item_type = Accessory::class; $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php index f77c4cc00f..0d01428ea8 100644 --- a/app/Observers/AssetObserver.php +++ b/app/Observers/AssetObserver.php @@ -62,7 +62,7 @@ class AssetObserver $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->log_meta = json_encode($changed); $logAction->logaction('update'); } @@ -108,7 +108,7 @@ class AssetObserver $logAction->item_type = Asset::class; // can we instead say $logAction->item = $asset ? $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($asset->imported) { $logAction->setActionSource('importer'); } @@ -127,7 +127,7 @@ class AssetObserver $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } @@ -143,7 +143,7 @@ class AssetObserver $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('restore'); } diff --git a/app/Observers/ComponentObserver.php b/app/Observers/ComponentObserver.php index 44bf819353..cd2c58c367 100644 --- a/app/Observers/ComponentObserver.php +++ b/app/Observers/ComponentObserver.php @@ -20,7 +20,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('update'); } @@ -37,7 +37,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($component->imported) { $logAction->setActionSource('importer'); } @@ -56,7 +56,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/ConsumableObserver.php b/app/Observers/ConsumableObserver.php index 377995ebb9..57471cee9c 100644 --- a/app/Observers/ConsumableObserver.php +++ b/app/Observers/ConsumableObserver.php @@ -34,7 +34,7 @@ class ConsumableObserver $logAction->item_type = Consumable::class; $logAction->item_id = $consumable->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->log_meta = json_encode($changed); $logAction->logaction('update'); } @@ -53,7 +53,7 @@ class ConsumableObserver $logAction->item_type = Consumable::class; $logAction->item_id = $consumable->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($consumable->imported) { $logAction->setActionSource('importer'); } @@ -98,7 +98,7 @@ class ConsumableObserver $logAction->item_type = Consumable::class; $logAction->item_id = $consumable->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/LicenseObserver.php b/app/Observers/LicenseObserver.php index de4863fafa..4e355bf639 100644 --- a/app/Observers/LicenseObserver.php +++ b/app/Observers/LicenseObserver.php @@ -20,7 +20,7 @@ class LicenseObserver $logAction->item_type = License::class; $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('update'); } @@ -37,7 +37,7 @@ class LicenseObserver $logAction->item_type = License::class; $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($license->imported) { $logAction->setActionSource('importer'); } @@ -56,7 +56,7 @@ class LicenseObserver $logAction->item_type = License::class; $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index c7c2a460cb..acde9ceaed 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -83,7 +83,7 @@ class UserObserver $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->target_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->log_meta = json_encode($changed); $logAction->logaction('update'); } @@ -105,7 +105,7 @@ class UserObserver $logAction->item_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->item_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('create'); } @@ -123,7 +123,7 @@ class UserObserver $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->target_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } @@ -141,7 +141,7 @@ class UserObserver $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->target_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('restore'); } diff --git a/app/Presenters/AccessoryPresenter.php b/app/Presenters/AccessoryPresenter.php index 4ff3c699c7..04f55cf364 100644 --- a/app/Presenters/AccessoryPresenter.php +++ b/app/Presenters/AccessoryPresenter.php @@ -127,6 +127,29 @@ class AccessoryPresenter extends Presenter 'visible' => false, 'title' => trans('general.notes'), 'formatter' => 'notesFormatter' + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'change', 'searchable' => false, diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index ebbe3d7823..37a1adbc28 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -7,7 +7,7 @@ namespace App\Presenters; */ class ActionlogPresenter extends Presenter { - public function admin() + public function adminuser() { if ($user = $this->model->user) { if (empty($user->deleted_at)) { diff --git a/app/Presenters/AssetMaintenancesPresenter.php b/app/Presenters/AssetMaintenancesPresenter.php index 3908720dc3..ca49c931a4 100644 --- a/app/Presenters/AssetMaintenancesPresenter.php +++ b/app/Presenters/AssetMaintenancesPresenter.php @@ -117,12 +117,29 @@ class AssetMaintenancesPresenter extends Presenter 'title' => trans('admin/asset_maintenances/form.cost'), 'class' => 'text-right', ], [ - 'field' => 'user_id', - 'searchable' => true, + 'field' => 'created_by', + 'searchable' => false, 'sortable' => true, - 'title' => trans('general.admin'), + 'title' => trans('general.created_by'), + 'visible' => false, 'formatter' => 'usersLinkObjFormatter', ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ],[ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php index da93092b91..324cc7d096 100644 --- a/app/Presenters/AssetModelPresenter.php +++ b/app/Presenters/AssetModelPresenter.php @@ -135,19 +135,27 @@ class AssetModelPresenter extends Presenter 'formatter' => 'notesFormatter', ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ 'field' => 'created_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.created_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', - ], - [ + ], [ 'field' => 'updated_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.updated_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index e55cb00c2e..19bd2985e7 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -233,18 +233,28 @@ class AssetPresenter extends Presenter 'title' => trans('general.user_requests_count'), ], [ - 'field' => 'created_at', + 'field' => 'created_by', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.created_by'), 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, 'title' => trans('general.created_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'updated_at', - 'searchable' => false, + 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.updated_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'last_checkout', diff --git a/app/Presenters/CategoryPresenter.php b/app/Presenters/CategoryPresenter.php index fbf431637c..f551c0ba1b 100644 --- a/app/Presenters/CategoryPresenter.php +++ b/app/Presenters/CategoryPresenter.php @@ -77,19 +77,28 @@ class CategoryPresenter extends Presenter "title" => trans('admin/categories/general.use_default_eula_column'), 'visible' => true, "formatter" => 'trueFalseFormatter', + ],[ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', ], [ 'field' => 'created_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.created_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'updated_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.updated_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'actions', diff --git a/app/Presenters/CompanyPresenter.php b/app/Presenters/CompanyPresenter.php index bcb77c7eba..6f9ece2141 100644 --- a/app/Presenters/CompanyPresenter.php +++ b/app/Presenters/CompanyPresenter.php @@ -105,20 +105,29 @@ class CompanyPresenter extends Presenter 'title' => trans('general.components'), 'visible' => true, 'class' => 'css-component', - ], [ - 'field' => 'updated_at', + ],[ + 'field' => 'created_by', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.created_by'), 'visible' => false, - 'title' => trans('general.updated_at'), - 'formatter' => 'createdAtFormatter', + 'formatter' => 'usersLinkObjFormatter', ], [ 'field' => 'created_at', - 'searchable' => false, + 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.created_at'), - 'formatter' => 'createdAtFormatter', + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'actions', 'searchable' => false, diff --git a/app/Presenters/ComponentPresenter.php b/app/Presenters/ComponentPresenter.php index d142d7abc2..f32bb56d57 100644 --- a/app/Presenters/ComponentPresenter.php +++ b/app/Presenters/ComponentPresenter.php @@ -119,6 +119,27 @@ class ComponentPresenter extends Presenter 'visible' => false, 'title' => trans('general.notes'), 'formatter' => 'notesFormatter', + ],[ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ],[ + 'field' => 'created_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.created_at'), + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.updated_at'), + 'formatter' => 'dateDisplayFormatter', ], ]; diff --git a/app/Presenters/ConsumablePresenter.php b/app/Presenters/ConsumablePresenter.php index dc22c69e24..cab8bed8bb 100644 --- a/app/Presenters/ConsumablePresenter.php +++ b/app/Presenters/ConsumablePresenter.php @@ -131,6 +131,27 @@ class ConsumablePresenter extends Presenter 'visible' => false, 'title' => trans('general.notes'), 'formatter' => 'notesFormatter', + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ],[ + 'field' => 'created_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.created_at'), + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.updated_at'), + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'change', 'searchable' => false, diff --git a/app/Presenters/DepreciationPresenter.php b/app/Presenters/DepreciationPresenter.php index cfba531623..3f240fcc53 100644 --- a/app/Presenters/DepreciationPresenter.php +++ b/app/Presenters/DepreciationPresenter.php @@ -65,8 +65,30 @@ class DepreciationPresenter extends Presenter 'sortable' => true, 'title' => trans('general.licenses'), 'visible' => true, - ], - [ + ],[ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/DepreciationReportPresenter.php b/app/Presenters/DepreciationReportPresenter.php index 6905385615..1d4c459614 100644 --- a/app/Presenters/DepreciationReportPresenter.php +++ b/app/Presenters/DepreciationReportPresenter.php @@ -140,7 +140,7 @@ class DepreciationReportPresenter extends Presenter ], [ "field" => "book_value", "searchable" => true, - "sortable" => true, + "sortable" => false, "visible" => true, "title" => trans('admin/hardware/table.book_value'), "footerFormatter" => 'sumFormatter', diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php index 1545cabd30..4256c2c686 100644 --- a/app/Presenters/LicensePresenter.php +++ b/app/Presenters/LicensePresenter.php @@ -162,7 +162,7 @@ class LicensePresenter extends Presenter 'field' => 'created_by', 'searchable' => false, 'sortable' => true, - 'title' => trans('general.admin'), + 'title' => trans('general.created_by'), 'visible' => false, 'formatter' => 'usersLinkObjFormatter', ], [ diff --git a/app/Presenters/ManufacturerPresenter.php b/app/Presenters/ManufacturerPresenter.php index 07a22c9ea4..ea29974f34 100644 --- a/app/Presenters/ManufacturerPresenter.php +++ b/app/Presenters/ManufacturerPresenter.php @@ -126,6 +126,13 @@ class ManufacturerPresenter extends Presenter 'class' => 'css-accessory', ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ 'field' => 'created_at', 'searchable' => true, 'sortable' => true, @@ -133,9 +140,15 @@ class ManufacturerPresenter extends Presenter 'title' => trans('general.created_at'), 'visible' => false, 'formatter' => 'dateDisplayFormatter', - ], - - [ + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/PredefinedKitPresenter.php b/app/Presenters/PredefinedKitPresenter.php index b234653adf..7ce7d8c23d 100644 --- a/app/Presenters/PredefinedKitPresenter.php +++ b/app/Presenters/PredefinedKitPresenter.php @@ -27,6 +27,29 @@ class PredefinedKitPresenter extends Presenter 'sortable' => true, 'title' => trans('general.name'), 'formatter' => 'kitsLinkFormatter', + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], ]; diff --git a/app/Presenters/StatusLabelPresenter.php b/app/Presenters/StatusLabelPresenter.php new file mode 100644 index 0000000000..2e43400041 --- /dev/null +++ b/app/Presenters/StatusLabelPresenter.php @@ -0,0 +1,115 @@ + 'id', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], [ + 'field' => 'name', + 'searchable' => true, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('general.name'), + 'visible' => true, + 'formatter' => 'statuslabelsAssetLinkFormatter', + ],[ + 'field' => 'type', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('admin/statuslabels/table.status_type'), + 'visible' => true, + 'formatter' => 'statusLabelTypeFormatter', + ], [ + 'field' => 'assets_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('general.assets'), + 'visible' => true, + ], [ + 'field' => 'color', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/statuslabels/table.color'), + 'visible' => true, + 'formatter' => 'colorSqFormatter', + ], [ + 'field' => 'show_in_nav', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/statuslabels/table.show_in_nav'), + 'visible' => true, + 'formatter' => 'trueFalseFormatter', + ], [ + 'field' => 'default_label', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/statuslabels/table.default_label'), + 'visible' => true, + 'formatter' => 'trueFalseFormatter', + ],[ + 'field' => 'notes', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.notes'), + 'visible' => false, + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'formatter' => 'statuslabelsActionsFormatter', + ], + ]; + + return json_encode($layout); + } + + +} diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index 12d833e60b..7ee05da0cb 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -361,6 +361,14 @@ class UserPresenter extends Presenter 'title' => trans('general.created_at'), 'visible' => false, 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'start_date', diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php index 1f3abca8a6..76ba1b629a 100644 --- a/app/Providers/ValidationServiceProvider.php +++ b/app/Providers/ValidationServiceProvider.php @@ -31,6 +31,7 @@ class ValidationServiceProvider extends ServiceProvider Validator::extend('email_array', function ($attribute, $value, $parameters, $validator) { $value = str_replace(' ', '', $value); $array = explode(',', $value); + $email_to_validate = []; foreach ($array as $email) { //loop over values $email_to_validate['alert_email'][] = $email; @@ -38,7 +39,7 @@ class ValidationServiceProvider extends ServiceProvider $rules = ['alert_email.*'=>'email']; $messages = [ - 'alert_email.*'=>trans('validation.email_array'), + 'alert_email.*' => trans('validation.custom.email_array'), ]; $validator = Validator::make($email_to_validate, $rules, $messages); diff --git a/app/Rules/AssetCannotBeCheckedOutToNondeployableStatus.php b/app/Rules/AssetCannotBeCheckedOutToNondeployableStatus.php new file mode 100644 index 0000000000..c2c451b82b --- /dev/null +++ b/app/Rules/AssetCannotBeCheckedOutToNondeployableStatus.php @@ -0,0 +1,51 @@ + + */ + protected $data = []; + + + /** + * Set the data under validation. + * + * @param array $data + */ + public function setData(array $data): static + { + $this->data = $data; + return $this; + } + + /** + * Run the validation rule. + * + * @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail + */ + public function validate(string $attribute, mixed $value, Closure $fail): void + { + // Check to see if any of the assign-ish fields are set + if ((isset($this->data['assigned_to'])) || (isset($this->data['assigned_user'])) || (isset($this->data['assigned_location'])) || (isset($this->data['assigned_asset'])) || (isset($this->data['assigned_type']))) { + + if (($value) && ($label = Statuslabel::find($value)) && ($label->getStatuslabelType()!='deployable')) { + $fail(trans('admin/hardware/form.asset_not_deployable')); + } + + } + + + } +} diff --git a/app/Services/PredefinedKitCheckoutService.php b/app/Services/PredefinedKitCheckoutService.php index d683875395..2cf4593687 100644 --- a/app/Services/PredefinedKitCheckoutService.php +++ b/app/Services/PredefinedKitCheckoutService.php @@ -157,7 +157,7 @@ class PredefinedKitCheckoutService } // licenses foreach ($license_seats_to_add as $licenseSeat) { - $licenseSeat->user_id = $admin->id; + $licenseSeat->created_by = $admin->id; $licenseSeat->assigned_to = $user->id; if ($licenseSeat->save()) { event(new CheckoutableCheckedOut($licenseSeat, $user, $admin, $note)); diff --git a/composer.lock b/composer.lock index 715070df0f..3f79921b26 100644 --- a/composer.lock +++ b/composer.lock @@ -137,16 +137,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.5", + "version": "v1.2.6", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b" + "reference": "a63485b65b6b3367039306496d49737cf1995408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", - "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", + "reference": "a63485b65b6b3367039306496d49737cf1995408", "shasum": "" }, "require": { @@ -185,22 +185,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.5" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" }, - "time": "2024-04-19T21:30:56+00:00" + "time": "2024-06-13T17:21:28+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.314.6", + "version": "3.323.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "d04da330b0201edab71edd33a03b8d5ad6e4a313" + "reference": "e66ee025b1d169fad3c784934f56648d3eec11ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d04da330b0201edab71edd33a03b8d5ad6e4a313", - "reference": "d04da330b0201edab71edd33a03b8d5ad6e4a313", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e66ee025b1d169fad3c784934f56648d3eec11ae", + "reference": "e66ee025b1d169fad3c784934f56648d3eec11ae", "shasum": "" }, "require": { @@ -253,7 +253,10 @@ ], "psr-4": { "Aws\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/data/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -280,9 +283,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.314.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.323.4" }, - "time": "2024-06-20T18:09:51+00:00" + "time": "2024-10-09T18:10:22+00:00" }, { "name": "bacon/bacon-qr-code", @@ -340,23 +343,23 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.13.5", + "version": "v3.14.3", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07" + "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd", + "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd", "shasum": "" }, "require": { "illuminate/routing": "^9|^10|^11", "illuminate/session": "^9|^10|^11", "illuminate/support": "^9|^10|^11", - "maximebf/debugbar": "~1.22.0", + "maximebf/debugbar": "~1.23.0", "php": "^8.0", "symfony/finder": "^6|^7" }, @@ -369,7 +372,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.13-dev" + "dev-master": "3.14-dev" }, "laravel": { "providers": [ @@ -408,7 +411,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.3" }, "funding": [ { @@ -420,7 +423,7 @@ "type": "github" } ], - "time": "2024-04-12T11:20:37+00:00" + "time": "2024-10-02T09:17:49+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -630,23 +633,23 @@ }, { "name": "dasprid/enum", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90", "shasum": "" }, "require": { "php": ">=7.1 <9.0" }, "require-dev": { - "phpunit/phpunit": "^7 | ^8 | ^9", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", "squizlabs/php_codesniffer": "*" }, "type": "library", @@ -674,9 +677,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.6" }, - "time": "2023-08-25T16:18:39+00:00" + "time": "2024-08-09T14:30:48+00:00" }, { "name": "defuse/php-encryption", @@ -747,16 +750,16 @@ }, { "name": "dflydev/dot-access-data", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "f41715465d65213d644d3141a6a93081be5d3549" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", - "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -816,9 +819,9 @@ ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2022-10-27T11:44:00+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "doctrine/cache", @@ -921,16 +924,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.6", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1" + "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b7411825cf7efb7e51f9791dea19d86e43b399a1", - "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", + "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", "shasum": "" }, "require": { @@ -946,12 +949,12 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.11.5", + "phpstan/phpstan": "1.12.0", "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "9.6.19", + "phpunit/phpunit": "9.6.20", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.1", + "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/console": "^4.4|^5.4|^6.0|^7.0", "vimeo/psalm": "4.30.0" @@ -1014,7 +1017,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.6" + "source": "https://github.com/doctrine/dbal/tree/3.9.1" }, "funding": [ { @@ -1030,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T10:38:17+00:00" + "time": "2024-09-01T13:49:23+00:00" }, { "name": "doctrine/deprecations", @@ -1472,16 +1475,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + "reference": "8c784d071debd117328803d86b2097615b457500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", "shasum": "" }, "require": { @@ -1494,10 +1497,14 @@ "require-dev": { "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -1521,7 +1528,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" }, "funding": [ { @@ -1529,7 +1536,7 @@ "type": "github" } ], - "time": "2023-08-10T19:36:49+00:00" + "time": "2024-10-09T13:47:03+00:00" }, { "name": "eduardokum/laravel-mail-auto-embed", @@ -1765,26 +1772,26 @@ }, { "name": "filp/whoops", - "version": "2.15.4", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", + "php": "^7.1 || ^8.0", "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -1824,7 +1831,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.4" + "source": "https://github.com/filp/whoops/tree/2.16.0" }, "funding": [ { @@ -1832,7 +1839,7 @@ "type": "github" } ], - "time": "2023-11-03T12:00:00+00:00" + "time": "2024-09-25T12:00:00+00:00" }, { "name": "firebase/php-jwt", @@ -1970,24 +1977,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.2", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2" + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "autoload": { @@ -2016,7 +2023,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -2028,26 +2035,26 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:16:48+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2058,9 +2065,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -2138,7 +2145,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -2154,20 +2161,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", "shasum": "" }, "require": { @@ -2175,7 +2182,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -2221,7 +2228,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.3" }, "funding": [ { @@ -2237,20 +2244,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-07-18T10:29:17+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -2265,8 +2272,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2337,7 +2344,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -2353,7 +2360,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2675,16 +2682,16 @@ }, { "name": "laravel/framework", - "version": "v10.48.14", + "version": "v10.48.22", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "27cb4736bb7e60a5311ec73160068dfbcf98336b" + "reference": "c4ea52bb044faef4a103d7dd81746c01b2ec860e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/27cb4736bb7e60a5311ec73160068dfbcf98336b", - "reference": "27cb4736bb7e60a5311ec73160068dfbcf98336b", + "url": "https://api.github.com/repos/laravel/framework/zipball/c4ea52bb044faef4a103d7dd81746c01b2ec860e", + "reference": "c4ea52bb044faef4a103d7dd81746c01b2ec860e", "shasum": "" }, "require": { @@ -2878,7 +2885,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-06-21T10:06:42+00:00" + "time": "2024-09-12T15:00:09+00:00" }, { "name": "laravel/helpers", @@ -3017,16 +3024,16 @@ }, { "name": "laravel/prompts", - "version": "v0.1.24", + "version": "v0.1.25", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "409b0b4305273472f3754826e68f4edbd0150149" + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149", - "reference": "409b0b4305273472f3754826e68f4edbd0150149", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", "shasum": "" }, "require": { @@ -3069,32 +3076,33 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.24" + "source": "https://github.com/laravel/prompts/tree/v0.1.25" }, - "time": "2024-06-17T13:58:22+00:00" + "time": "2024-08-12T22:06:33+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.3", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", "shasum": "" }, "require": { "php": "^7.3|^8.0" }, "require-dev": { - "nesbot/carbon": "^2.61", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", "pestphp/pest": "^1.21.3", "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11" + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" }, "type": "library", "extra": { @@ -3131,7 +3139,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-11-08T14:08:06+00:00" + "time": "2024-09-23T13:33:08+00:00" }, { "name": "laravel/slack-notification-channel", @@ -3196,16 +3204,16 @@ }, { "name": "laravel/socialite", - "version": "v5.15.0", + "version": "v5.16.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "c8234bfb286a8210df8d62f94562c71bfda4a446" + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/c8234bfb286a8210df8d62f94562c71bfda4a446", - "reference": "c8234bfb286a8210df8d62f94562c71bfda4a446", + "url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", "shasum": "" }, "require": { @@ -3264,20 +3272,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-06-11T13:33:20+00:00" + "time": "2024-09-03T09:46:57+00:00" }, { "name": "laravel/tinker", - "version": "v2.9.0", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5", + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5", "shasum": "" }, "require": { @@ -3328,9 +3336,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.9.0" + "source": "https://github.com/laravel/tinker/tree/v2.10.0" }, - "time": "2024-01-04T16:10:04+00:00" + "time": "2024-09-23T13:32:56+00:00" }, { "name": "laravel/ui", @@ -3543,16 +3551,16 @@ }, { "name": "league/commonmark", - "version": "2.4.2", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", "shasum": "" }, "require": { @@ -3565,8 +3573,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.3", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -3588,7 +3596,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -3645,7 +3653,7 @@ "type": "tidelift" } ], - "time": "2024-02-02T11:59:32+00:00" + "time": "2024-08-16T11:46:16+00:00" }, { "name": "league/config", @@ -3731,16 +3739,16 @@ }, { "name": "league/csv", - "version": "9.16.0", + "version": "9.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "998280c6c34bd67d8125fdc8b45bae28d761b440" + "reference": "8cab815fb11ec93aa2f7b8a57b3daa1f1a364011" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/998280c6c34bd67d8125fdc8b45bae28d761b440", - "reference": "998280c6c34bd67d8125fdc8b45bae28d761b440", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/8cab815fb11ec93aa2f7b8a57b3daa1f1a364011", + "reference": "8cab815fb11ec93aa2f7b8a57b3daa1f1a364011", "shasum": "" }, "require": { @@ -3748,17 +3756,16 @@ "php": "^8.1.2" }, "require-dev": { - "doctrine/collections": "^2.2.2", "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.57.1", - "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.11.1", - "phpstan/phpstan-deprecation-rules": "^1.2.0", + "friendsofphp/php-cs-fixer": "^3.64.0", + "phpbench/phpbench": "^1.3.1", + "phpstan/phpstan": "^1.12.5", + "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.0", - "phpunit/phpunit": "^10.5.16 || ^11.1.3", - "symfony/var-dumper": "^6.4.6 || ^7.0.7" + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^10.5.16 || ^11.4.0", + "symfony/var-dumper": "^6.4.8 || ^7.1.5" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -3776,7 +3783,7 @@ "src/functions_include.php" ], "psr-4": { - "League\\Csv\\": "src" + "League\\Csv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3815,7 +3822,7 @@ "type": "github" } ], - "time": "2024-05-24T11:04:54+00:00" + "time": "2024-10-10T10:30:28+00:00" }, { "name": "league/event", @@ -3873,16 +3880,16 @@ }, { "name": "league/flysystem", - "version": "3.28.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", "shasum": "" }, "require": { @@ -3950,22 +3957,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" }, - "time": "2024-05-22T10:09:12+00:00" + "time": "2024-10-08T08:58:34+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "22071ef1604bc776f5ff2468ac27a752514665c8" + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8", - "reference": "22071ef1604bc776f5ff2468ac27a752514665c8", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9", + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9", "shasum": "" }, "require": { @@ -4005,22 +4012,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-17T13:10:48+00:00" }, { "name": "league/flysystem-local", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", "shasum": "" }, "require": { @@ -4054,22 +4061,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-09T21:24:39+00:00" }, { "name": "league/mime-type-detection", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { @@ -4100,7 +4107,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -4112,7 +4119,7 @@ "type": "tidelift" } ], - "time": "2024-01-28T23:22:08+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { "name": "league/oauth1-client", @@ -4454,16 +4461,16 @@ }, { "name": "livewire/livewire", - "version": "v3.5.1", + "version": "v3.5.9", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "da044261bb5c5449397f18fda3409f14acf47c0a" + "reference": "d04a229058afa76116d0e39209943a8ea3a7f888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/da044261bb5c5449397f18fda3409f14acf47c0a", - "reference": "da044261bb5c5449397f18fda3409f14acf47c0a", + "url": "https://api.github.com/repos/livewire/livewire/zipball/d04a229058afa76116d0e39209943a8ea3a7f888", + "reference": "d04a229058afa76116d0e39209943a8ea3a7f888", "shasum": "" }, "require": { @@ -4471,6 +4478,7 @@ "illuminate/routing": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0", "illuminate/validation": "^10.0|^11.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", "league/mime-type-detection": "^1.9", "php": "^8.1", "symfony/console": "^6.0|^7.0", @@ -4479,7 +4487,6 @@ "require-dev": { "calebporzio/sushi": "^2.1", "laravel/framework": "^10.15.0|^11.0", - "laravel/prompts": "^0.1.6", "mockery/mockery": "^1.3.1", "orchestra/testbench": "^8.21.0|^9.0", "orchestra/testbench-dusk": "^8.24|^9.1", @@ -4518,7 +4525,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.1" + "source": "https://github.com/livewire/livewire/tree/v3.5.9" }, "funding": [ { @@ -4526,7 +4533,7 @@ "type": "github" } ], - "time": "2024-06-18T11:10:42+00:00" + "time": "2024-10-01T12:40:06+00:00" }, { "name": "masterminds/html5", @@ -4597,16 +4604,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.22.3", + "version": "v1.23.2", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96" + "reference": "689720d724c771ac4add859056744b7b3f2406da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", - "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da", + "reference": "689720d724c771ac4add859056744b7b3f2406da", "shasum": "" }, "require": { @@ -4628,7 +4635,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.22-dev" + "dev-master": "1.23-dev" } }, "autoload": { @@ -4659,22 +4666,22 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2" }, - "time": "2024-04-03T19:39:26+00:00" + "time": "2024-09-16T11:23:09+00:00" }, { "name": "monolog/monolog", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", "shasum": "" }, "require": { @@ -4750,7 +4757,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.6.0" + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" }, "funding": [ { @@ -4762,20 +4769,20 @@ "type": "tidelift" } ], - "time": "2024-04-12T21:02:21+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { @@ -4792,7 +4799,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -4826,9 +4833,9 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" }, - "time": "2023-08-25T10:54:48+00:00" + "time": "2024-09-04T18:46:31+00:00" }, { "name": "neitanod/forceutf8", @@ -4980,24 +4987,24 @@ }, { "name": "nette/schema", - "version": "v1.3.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.3" + "php": "8.1 - 8.4" }, "require-dev": { - "nette/tester": "^2.4", + "nette/tester": "^2.5.2", "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, @@ -5036,26 +5043,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.0" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2023-12-11T11:54:22+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", - "version": "v4.0.4", + "version": "v4.0.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", "shasum": "" }, "require": { - "php": ">=8.0 <8.4" + "php": "8.0 - 8.4" }, "conflict": { "nette/finder": "<3", @@ -5122,22 +5129,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.4" + "source": "https://github.com/nette/utils/tree/v4.0.5" }, - "time": "2024-01-17T16:50:36+00:00" + "time": "2024-08-07T15:39:19+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { @@ -5146,7 +5153,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -5178,9 +5185,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "nunomaduro/collision", @@ -5366,16 +5373,16 @@ }, { "name": "nyholm/psr7", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { @@ -5428,7 +5435,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.1" + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { @@ -5440,7 +5447,7 @@ "type": "github" } ], - "time": "2023-11-13T09:31:12+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { "name": "okvpn/clock-lts", @@ -6035,16 +6042,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, "require": { @@ -6052,13 +6059,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -6094,7 +6101,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" }, "funding": [ { @@ -6106,20 +6113,20 @@ "type": "tidelift" } ], - "time": "2023-11-12T21:59:55+00:00" + "time": "2024-07-20T21:41:07+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.38", + "version": "3.0.42", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "b18b8788e51156c4dd97b7f220a31149a0052067" + "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b18b8788e51156c4dd97b7f220a31149a0052067", - "reference": "b18b8788e51156c4dd97b7f220a31149a0052067", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98", + "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98", "shasum": "" }, "require": { @@ -6200,7 +6207,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.38" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42" }, "funding": [ { @@ -6216,7 +6223,7 @@ "type": "tidelift" } ], - "time": "2024-06-17T10:11:32+00:00" + "time": "2024-09-16T03:06:04+00:00" }, { "name": "phpspec/prophecy", @@ -6289,16 +6296,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "1.32.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", "shasum": "" }, "require": { @@ -6330,30 +6337,30 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-09-26T07:23:32+00:00" }, { "name": "pragmarx/google2fa", - "version": "v8.0.1", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", - "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^1.0|^2.0", + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", "php": "^7.1|^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.18", + "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^7.5.15|^8.5|^9.0" }, "type": "library", @@ -6382,9 +6389,9 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3" }, - "time": "2022-06-13T21:57:56+00:00" + "time": "2024-09-05T11:56:40+00:00" }, { "name": "pragmarx/google2fa-laravel", @@ -6885,16 +6892,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -6929,9 +6936,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -7467,16 +7474,16 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v8.5.1", + "version": "v8.6.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152" + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", "shasum": "" }, "require": { @@ -7526,22 +7533,22 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" }, - "time": "2024-02-15T16:41:13+00:00" + "time": "2024-07-01T07:33:21+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", "shasum": "" }, "require": { @@ -7552,7 +7559,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.4" }, "type": "library", "extra": { @@ -7597,7 +7604,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" }, "funding": [ { @@ -7605,7 +7612,7 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-08-12T06:03:08+00:00" }, { "name": "sebastian/diff", @@ -7817,16 +7824,16 @@ }, { "name": "spatie/backtrace", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23" + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/8373b9d51638292e3bfd736a9c19a654111b4a23", - "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", "shasum": "" }, "require": { @@ -7864,7 +7871,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.1" + "source": "https://github.com/spatie/backtrace/tree/1.6.2" }, "funding": [ { @@ -7876,20 +7883,20 @@ "type": "other" } ], - "time": "2024-04-24T13:22:11+00:00" + "time": "2024-07-22T08:21:24+00:00" }, { "name": "spatie/db-dumper", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "faca5056830bccea04eadf07e8074669cb9e905e" + "reference": "22553ab8c34a9bb70645cb9bc2d9f236f3135705" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/faca5056830bccea04eadf07e8074669cb9e905e", - "reference": "faca5056830bccea04eadf07e8074669cb9e905e", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/22553ab8c34a9bb70645cb9bc2d9f236f3135705", + "reference": "22553ab8c34a9bb70645cb9bc2d9f236f3135705", "shasum": "" }, "require": { @@ -7927,7 +7934,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.6.0" + "source": "https://github.com/spatie/db-dumper/tree/3.7.0" }, "funding": [ { @@ -7939,20 +7946,20 @@ "type": "github" } ], - "time": "2024-04-24T14:54:13+00:00" + "time": "2024-09-23T08:58:35+00:00" }, { "name": "spatie/error-solutions", - "version": "1.0.1", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "d60d4d2ef4b7701c86134ded959667cac6215233" + "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d60d4d2ef4b7701c86134ded959667cac6215233", - "reference": "d60d4d2ef4b7701c86134ded959667cac6215233", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/ae7393122eda72eed7cc4f176d1e96ea444f2d67", + "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67", "shasum": "" }, "require": { @@ -8005,7 +8012,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.0.1" + "source": "https://github.com/spatie/error-solutions/tree/1.1.1" }, "funding": [ { @@ -8013,20 +8020,20 @@ "type": "github" } ], - "time": "2024-06-21T10:09:00+00:00" + "time": "2024-07-25T11:06:04+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234" + "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", + "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", "shasum": "" }, "require": { @@ -8044,7 +8051,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" + "spatie/pest-plugin-snapshots": "^1.0|^2.0" }, "type": "library", "extra": { @@ -8074,7 +8081,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.7.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.8.0" }, "funding": [ { @@ -8082,7 +8089,7 @@ "type": "github" } ], - "time": "2024-06-12T14:39:14+00:00" + "time": "2024-08-01T08:27:26+00:00" }, { "name": "spatie/ignition", @@ -8169,16 +8176,16 @@ }, { "name": "spatie/laravel-backup", - "version": "8.8.1", + "version": "8.8.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-backup.git", - "reference": "a9c2d2f726f4c60c2dc5d7c0c8380f72492638c2" + "reference": "5b672713283703a74c629ccd67b1d77eb57e24b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/a9c2d2f726f4c60c2dc5d7c0c8380f72492638c2", - "reference": "a9c2d2f726f4c60c2dc5d7c0c8380f72492638c2", + "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/5b672713283703a74c629ccd67b1d77eb57e24b9", + "reference": "5b672713283703a74c629ccd67b1d77eb57e24b9", "shasum": "" }, "require": { @@ -8252,7 +8259,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-backup/issues", - "source": "https://github.com/spatie/laravel-backup/tree/8.8.1" + "source": "https://github.com/spatie/laravel-backup/tree/8.8.2" }, "funding": [ { @@ -8264,7 +8271,7 @@ "type": "other" } ], - "time": "2024-06-04T11:31:33+00:00" + "time": "2024-08-07T11:07:52+00:00" }, { "name": "spatie/laravel-ignition", @@ -8359,16 +8366,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.16.4", + "version": "1.16.5", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53" + "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c7413972cf22ffdff97b68499c22baa04eddb6a2", + "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2", "shasum": "" }, "require": { @@ -8407,7 +8414,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.5" }, "funding": [ { @@ -8415,7 +8422,7 @@ "type": "github" } ], - "time": "2024-03-20T07:29:11+00:00" + "time": "2024-08-27T18:56:10+00:00" }, { "name": "spatie/laravel-signal-aware-command", @@ -8554,16 +8561,16 @@ }, { "name": "symfony/console", - "version": "v6.4.10", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc" + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/504974cbe43d05f83b201d6498c206f16fc0cdbc", - "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc", + "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", "shasum": "" }, "require": { @@ -8628,7 +8635,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.10" + "source": "https://github.com/symfony/console/tree/v6.4.12" }, "funding": [ { @@ -8644,7 +8651,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:30:32+00:00" + "time": "2024-09-20T08:15:52+00:00" }, { "name": "symfony/css-selector", @@ -8781,16 +8788,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.8", + "version": "v6.4.10", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc" + "reference": "231f1b2ee80f72daa1972f7340297d67439224f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", - "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0", + "reference": "231f1b2ee80f72daa1972f7340297d67439224f0", "shasum": "" }, "require": { @@ -8836,7 +8843,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.8" + "source": "https://github.com/symfony/error-handler/tree/v6.4.10" }, "funding": [ { @@ -8852,7 +8859,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-07-26T12:30:32+00:00" }, { "name": "symfony/event-dispatcher", @@ -9012,16 +9019,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.8", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" + "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", + "url": "https://api.github.com/repos/symfony/finder/zipball/d7eb6daf8cd7e9ac4976e9576b32042ef7253453", + "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453", "shasum": "" }, "require": { @@ -9056,7 +9063,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.8" + "source": "https://github.com/symfony/finder/tree/v6.4.11" }, "funding": [ { @@ -9072,20 +9079,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-08-13T14:27:37+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "27de8cc95e11db7a50b027e71caaab9024545947" + "reference": "133ac043875f59c26c55e79cf074562127cce4d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/27de8cc95e11db7a50b027e71caaab9024545947", - "reference": "27de8cc95e11db7a50b027e71caaab9024545947", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/133ac043875f59c26c55e79cf074562127cce4d2", + "reference": "133ac043875f59c26c55e79cf074562127cce4d2", "shasum": "" }, "require": { @@ -9133,7 +9140,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.8" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.12" }, "funding": [ { @@ -9149,20 +9156,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-20T08:18:25+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1" + "reference": "96df83d51b5f78804f70c093b97310794fd6257b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", - "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96df83d51b5f78804f70c093b97310794fd6257b", + "reference": "96df83d51b5f78804f70c093b97310794fd6257b", "shasum": "" }, "require": { @@ -9247,7 +9254,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.12" }, "funding": [ { @@ -9263,20 +9270,20 @@ "type": "tidelift" } ], - "time": "2024-06-02T16:06:25+00:00" + "time": "2024-09-21T06:02:57+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "76326421d44c07f7824b19487cfbf87870b37efc" + "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/76326421d44c07f7824b19487cfbf87870b37efc", - "reference": "76326421d44c07f7824b19487cfbf87870b37efc", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b6a25408c569ae2366b3f663a4edad19420a9c26", + "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26", "shasum": "" }, "require": { @@ -9327,7 +9334,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.8" + "source": "https://github.com/symfony/mailer/tree/v6.4.12" }, "funding": [ { @@ -9343,20 +9350,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-08T12:30:05+00:00" }, { "name": "symfony/mime", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33" + "reference": "abe16ee7790b16aa525877419deb0f113953f0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/618597ab8b78ac86d1c75a9d0b35540cda074f33", - "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33", + "url": "https://api.github.com/repos/symfony/mime/zipball/abe16ee7790b16aa525877419deb0f113953f0e1", + "reference": "abe16ee7790b16aa525877419deb0f113953f0e1", "shasum": "" }, "require": { @@ -9370,7 +9377,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<6.3.2" + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", @@ -9380,7 +9387,7 @@ "symfony/process": "^5.4|^6.4|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3.2|^7.0" + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -9412,7 +9419,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.8" + "source": "https://github.com/symfony/mime/tree/v6.4.12" }, "funding": [ { @@ -9428,24 +9435,24 @@ "type": "tidelift" } ], - "time": "2024-06-01T07:50:16+00:00" + "time": "2024-09-20T08:18:25+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -9491,7 +9498,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -9507,24 +9514,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -9569,7 +9576,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -9585,26 +9592,25 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -9653,7 +9659,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -9669,24 +9675,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -9734,7 +9740,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -9750,24 +9756,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -9814,7 +9820,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -9830,97 +9836,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "10112722600777e02d2745716b70c5db4ca70442" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", - "reference": "10112722600777e02d2745716b70c5db4ca70442", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -9967,7 +9900,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -9983,24 +9916,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -10043,7 +9976,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -10059,24 +9992,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:35:24+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -10122,7 +10055,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -10138,20 +10071,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5" + "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "url": "https://api.github.com/repos/symfony/process/zipball/3f94e5f13ff58df371a7ead461b6e8068900fbb3", + "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3", "shasum": "" }, "require": { @@ -10183,7 +10116,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.8" + "source": "https://github.com/symfony/process/tree/v6.4.12" }, "funding": [ { @@ -10199,7 +10132,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -10292,16 +10225,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58" + "reference": "a7c8036bd159486228dc9be3e846a00a0dda9f9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", - "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", + "url": "https://api.github.com/repos/symfony/routing/zipball/a7c8036bd159486228dc9be3e846a00a0dda9f9f", + "reference": "a7c8036bd159486228dc9be3e846a00a0dda9f9f", "shasum": "" }, "require": { @@ -10355,7 +10288,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.8" + "source": "https://github.com/symfony/routing/tree/v6.4.12" }, "funding": [ { @@ -10371,7 +10304,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-20T08:32:26+00:00" }, { "name": "symfony/service-contracts", @@ -10458,16 +10391,16 @@ }, { "name": "symfony/string", - "version": "v6.4.10", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ccf9b30251719567bfd46494138327522b9a9446" + "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ccf9b30251719567bfd46494138327522b9a9446", - "reference": "ccf9b30251719567bfd46494138327522b9a9446", + "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", "shasum": "" }, "require": { @@ -10524,7 +10457,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.10" + "source": "https://github.com/symfony/string/tree/v6.4.12" }, "funding": [ { @@ -10540,20 +10473,20 @@ "type": "tidelift" } ], - "time": "2024-07-22T10:21:14+00:00" + "time": "2024-09-20T08:15:52+00:00" }, { "name": "symfony/translation", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a" + "reference": "cf8360b8352b086be620fae8342c4d96e391a489" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a002933b13989fc4bd0b58e04bf7eec5210e438a", - "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a", + "url": "https://api.github.com/repos/symfony/translation/zipball/cf8360b8352b086be620fae8342c4d96e391a489", + "reference": "cf8360b8352b086be620fae8342c4d96e391a489", "shasum": "" }, "require": { @@ -10619,7 +10552,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.8" + "source": "https://github.com/symfony/translation/tree/v6.4.12" }, "funding": [ { @@ -10635,7 +10568,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-16T06:02:54+00:00" }, { "name": "symfony/translation-contracts", @@ -10717,16 +10650,16 @@ }, { "name": "symfony/uid", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "url": "https://api.github.com/repos/symfony/uid/zipball/2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", + "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", "shasum": "" }, "require": { @@ -10771,7 +10704,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.8" + "source": "https://github.com/symfony/uid/tree/v6.4.12" }, "funding": [ { @@ -10787,20 +10720,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-20T08:32:26+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.8", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25" + "reference": "ee14c8254a480913268b1e3b1cba8045ed122694" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ad23ca4312395f0a8a8633c831ef4c4ee542ed25", - "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ee14c8254a480913268b1e3b1cba8045ed122694", + "reference": "ee14c8254a480913268b1e3b1cba8045ed122694", "shasum": "" }, "require": { @@ -10856,7 +10789,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.11" }, "funding": [ { @@ -10872,7 +10805,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-08-30T16:03:21+00:00" }, { "name": "tecnickcom/tc-lib-barcode", @@ -11045,16 +10978,16 @@ }, { "name": "tecnickcom/tcpdf", - "version": "6.7.5", + "version": "6.7.6", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36" + "reference": "4cf1ab192e87e6916d20f93077b2bdfa96a2f848" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", - "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/4cf1ab192e87e6916d20f93077b2bdfa96a2f848", + "reference": "4cf1ab192e87e6916d20f93077b2bdfa96a2f848", "shasum": "" }, "require": { @@ -11105,7 +11038,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.5" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.6" }, "funding": [ { @@ -11113,7 +11046,7 @@ "type": "custom" } ], - "time": "2024-04-20T17:25:10+00:00" + "time": "2024-10-06T10:54:28+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11427,23 +11360,23 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.2", + "graham-campbell/result-type": "^1.1.3", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2", + "phpoption/phpoption": "^1.9.3", "symfony/polyfill-ctype": "^1.24", "symfony/polyfill-mbstring": "^1.24", "symfony/polyfill-php80": "^1.24" @@ -11460,7 +11393,7 @@ "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "5.6-dev" @@ -11495,7 +11428,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { @@ -11507,7 +11440,7 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:43:29+00:00" + "time": "2024-07-20T21:52:34+00:00" }, { "name": "voku/portable-ascii", @@ -12083,30 +12016,38 @@ }, { "name": "composer/pcre", - "version": "3.1.4", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "04229f163664973f68f38f6f73d917799168ef24" + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", - "reference": "04229f163664973f68f38f6f73d917799168ef24", + "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -12134,7 +12075,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.4" + "source": "https://github.com/composer/pcre/tree/3.3.1" }, "funding": [ { @@ -12150,28 +12091,28 @@ "type": "tidelift" } ], - "time": "2024-05-27T13:40:54+00:00" + "time": "2024-08-27T18:44:43+00:00" }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -12215,7 +12156,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -12231,7 +12172,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/xdebug-handler", @@ -12571,16 +12512,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", + "version": "v1.5.3", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { @@ -12621,22 +12562,22 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" }, - "time": "2022-03-02T22:36:06+00:00" + "time": "2024-04-30T00:40:11+00:00" }, { "name": "fidry/cpu-core-counter", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { @@ -12676,7 +12617,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, "funding": [ { @@ -12684,20 +12625,20 @@ "type": "github" } ], - "time": "2024-02-07T09:43:46+00:00" + "time": "2024-08-06T10:04:20+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.59.3", + "version": "v3.64.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29" + "reference": "58dd9c931c785a79739310aef5178928305ffa67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/30ba9ecc2b0e5205e578fe29973c15653d9bfd29", - "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/58dd9c931c785a79739310aef5178928305ffa67", + "reference": "58dd9c931c785a79739310aef5178928305ffa67", "shasum": "" }, "require": { @@ -12779,7 +12720,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.59.3" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.64.0" }, "funding": [ { @@ -12787,7 +12728,7 @@ "type": "github" } ], - "time": "2024-06-16T14:17:03+00:00" + "time": "2024-08-30T23:09:38+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -12901,20 +12842,20 @@ }, { "name": "justinrainbow/json-schema", - "version": "v5.2.13", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", @@ -12925,11 +12866,6 @@ "bin/validate-json" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -12965,22 +12901,22 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" }, - "time": "2023-09-26T02:20:38+00:00" + "time": "2024-07-06T21:00:26+00:00" }, { "name": "larastan/larastan", - "version": "v2.9.7", + "version": "v2.9.8", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "5c805f636095cc2e0b659e3954775cf8f1dad1bb" + "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/5c805f636095cc2e0b659e3954775cf8f1dad1bb", - "reference": "5c805f636095cc2e0b659e3954775cf8f1dad1bb", + "url": "https://api.github.com/repos/larastan/larastan/zipball/340badd89b0eb5bddbc503a4829c08cf9a2819d7", + "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7", "shasum": "" }, "require": { @@ -12994,7 +12930,7 @@ "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.9.0", - "phpstan/phpstan": "^1.11.1" + "phpstan/phpstan": "^1.11.2" }, "require-dev": { "doctrine/coding-standard": "^12.0", @@ -13049,7 +12985,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.9.7" + "source": "https://github.com/larastan/larastan/tree/v2.9.8" }, "funding": [ { @@ -13069,7 +13005,7 @@ "type": "patreon" } ], - "time": "2024-05-27T18:33:26+00:00" + "time": "2024-07-06T17:46:02+00:00" }, { "name": "league/container", @@ -13298,16 +13234,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v4.4.1", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -13343,9 +13279,9 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nunomaduro/phpinsights", @@ -13841,16 +13777,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.9.0", + "version": "5.10.0", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "011fa18a4e55591fac6545a821921dd1d61c6984" + "reference": "91d980ab76c3f152481e367f62b921adc38af451" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/011fa18a4e55591fac6545a821921dd1d61c6984", - "reference": "011fa18a4e55591fac6545a821921dd1d61c6984", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/91d980ab76c3f152481e367f62b921adc38af451", + "reference": "91d980ab76c3f152481e367f62b921adc38af451", "shasum": "" }, "require": { @@ -13868,8 +13804,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.9.12", "phpstan/phpstan-phpunit": "^1.3.3", - "phpunit/php-code-coverage": "*", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^8.5 || ^9.6", "psalm/plugin-phpunit": "^0.16.1", "vimeo/psalm": "^4.11", "zumba/json-serializer": "~3.0.2" @@ -13925,20 +13860,20 @@ "type": "other" } ], - "time": "2024-01-20T20:34:02+00:00" + "time": "2024-08-29T20:56:34+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.12.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae", + "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae", "shasum": "" }, "require": { @@ -13983,36 +13918,36 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-10-06T15:03:59+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -14024,7 +13959,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -14053,7 +13988,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -14061,7 +13996,7 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -14308,16 +14243,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.29", + "version": "10.5.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f" + "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e9e80872b4e8064401788ee8a32d40b4455318f", - "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", + "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", "shasum": "" }, "require": { @@ -14331,14 +14266,14 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.15", + "phpunit/php-code-coverage": "^10.1.16", "phpunit/php-file-iterator": "^4.1.0", "phpunit/php-invoker": "^4.0.0", "phpunit/php-text-template": "^3.0.1", "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.1", + "sebastian/comparator": "^5.0.2", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", @@ -14389,7 +14324,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.29" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.36" }, "funding": [ { @@ -14405,7 +14340,7 @@ "type": "tidelift" } ], - "time": "2024-07-30T11:08:00+00:00" + "time": "2024-10-08T15:36:51+00:00" }, { "name": "react/cache", @@ -14781,31 +14716,31 @@ }, { "name": "react/socket", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.11", + "react/dns": "^1.13", "react/event-loop": "^1.2", - "react/promise": "^3 || ^2.6 || ^1.2.1", - "react/stream": "^1.2" + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4 || ^3 || ^2", + "react/async": "^4.3 || ^3.3 || ^2", "react/promise-stream": "^1.4", - "react/promise-timer": "^1.10" + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { @@ -14849,7 +14784,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.15.0" + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, "funding": [ { @@ -14857,7 +14792,7 @@ "type": "open_collective" } ], - "time": "2023-12-15T11:02:10+00:00" + "time": "2024-07-26T10:38:09+00:00" }, { "name": "react/stream", @@ -15703,16 +15638,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.1", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -15779,20 +15714,20 @@ "type": "open_collective" } ], - "time": "2024-05-22T21:24:41+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "symfony/cache", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "287142df5579ce223c485b3872df3efae8390984" + "reference": "a463451b7f6ac4a47b98dbfc78ec2d3560c759d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/287142df5579ce223c485b3872df3efae8390984", - "reference": "287142df5579ce223c485b3872df3efae8390984", + "url": "https://api.github.com/repos/symfony/cache/zipball/a463451b7f6ac4a47b98dbfc78ec2d3560c759d8", + "reference": "a463451b7f6ac4a47b98dbfc78ec2d3560c759d8", "shasum": "" }, "require": { @@ -15859,7 +15794,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.8" + "source": "https://github.com/symfony/cache/tree/v6.4.12" }, "funding": [ { @@ -15875,7 +15810,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-16T16:01:33+00:00" }, { "name": "symfony/cache-contracts", @@ -16029,16 +15964,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3" + "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d37529150e7081c51b3c5d5718c55a04a9503f3", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12", + "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12", "shasum": "" }, "require": { @@ -16075,7 +16010,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.8" + "source": "https://github.com/symfony/filesystem/tree/v6.4.12" }, "funding": [ { @@ -16091,20 +16026,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-16T16:01:33+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05" + "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", - "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", + "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", + "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", "shasum": "" }, "require": { @@ -16168,7 +16103,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.8" + "source": "https://github.com/symfony/http-client/tree/v6.4.12" }, "funding": [ { @@ -16184,7 +16119,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-20T08:21:33+00:00" }, { "name": "symfony/http-client-contracts", @@ -16333,20 +16268,20 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -16389,7 +16324,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -16405,7 +16340,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/stopwatch", @@ -16471,16 +16406,16 @@ }, { "name": "symfony/var-exporter", - "version": "v6.4.8", + "version": "v6.4.9", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "792ca836f99b340f2e9ca9497c7953948c49a504" + "reference": "f9a060622e0d93777b7f8687ec4860191e16802e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/792ca836f99b340f2e9ca9497c7953948c49a504", - "reference": "792ca836f99b340f2e9ca9497c7953948c49a504", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e", + "reference": "f9a060622e0d93777b7f8687ec4860191e16802e", "shasum": "" }, "require": { @@ -16528,7 +16463,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.8" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.9" }, "funding": [ { @@ -16544,7 +16479,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-06-24T15:53:56+00:00" }, { "name": "theseer/tokenizer", @@ -16598,16 +16533,16 @@ }, { "name": "vimeo/psalm", - "version": "5.25.0", + "version": "5.26.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505" + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", - "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", "shasum": "" }, "require": { @@ -16628,7 +16563,7 @@ "felixfbecker/language-server-protocol": "^1.5.2", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", + "nikic/php-parser": "^4.17", "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", @@ -16704,7 +16639,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-06-16T15:08:35+00:00" + "time": "2024-09-08T18:53:08+00:00" } ], "aliases": [], diff --git a/config/version.php b/config/version.php index d303fcc808..e7eea2adfb 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v7.0.12', - 'full_app_version' => 'v7.0.12 - build 15249-g713bb104e', - 'build_version' => '15249', + 'app_version' => 'v7.0.13', + 'full_app_version' => 'v7.0.13 - build 15514-gdc0949da7', + 'build_version' => '15514', 'prerelease_version' => '', - 'hash_version' => 'g713bb104e', - 'full_hash' => 'v7.0.12-338-g713bb104e', + 'hash_version' => 'gdc0949da7', + 'full_hash' => 'v7.0.13-265-gdc0949da7', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/AccessoryFactory.php b/database/factories/AccessoryFactory.php index 356b367ec4..6442472d5f 100644 --- a/database/factories/AccessoryFactory.php +++ b/database/factories/AccessoryFactory.php @@ -3,7 +3,6 @@ namespace Database\Factories; use App\Models\Accessory; -use App\Models\AccessoryCheckout; use App\Models\Category; use App\Models\Location; use App\Models\Manufacturer; @@ -34,7 +33,7 @@ class AccessoryFactory extends Factory $this->faker->randomElement(['Bluetooth', 'Wired']), $this->faker->randomElement(['Keyboard', 'Wired']) ), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'category_id' => Category::factory()->forAccessories(), 'model_number' => $this->faker->numberBetween(1000000, 50000000), 'location_id' => Location::factory(), @@ -129,7 +128,7 @@ class AccessoryFactory extends Factory $accessory->checkouts()->create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), - 'user_id' => $user->id, + 'created_by' => $user->id, 'assigned_to' => $user->id, 'assigned_type' => User::class, 'note' => '', @@ -150,10 +149,25 @@ class AccessoryFactory extends Factory $accessory->checkouts()->create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), - 'user_id' => 1, + 'created_by' => 1, 'assigned_to' => $user->id ?? User::factory()->create()->id, 'assigned_type' => User::class, ]); }); } + + public function checkedOutToUsers(array $users) + { + return $this->afterCreating(function (Accessory $accessory) use ($users) { + foreach ($users as $user) { + $accessory->checkouts()->create([ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'user_id' => 1, + 'assigned_to' => $user->id, + 'assigned_type' => User::class, + ]); + } + }); + } } diff --git a/database/factories/ActionlogFactory.php b/database/factories/ActionlogFactory.php index a88166d14b..ad07f7082b 100644 --- a/database/factories/ActionlogFactory.php +++ b/database/factories/ActionlogFactory.php @@ -29,7 +29,7 @@ class ActionlogFactory extends Factory return [ 'item_id' => Asset::factory(), 'item_type' => Asset::class, - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'action_type' => 'uploaded', ]; } @@ -92,7 +92,7 @@ class ActionlogFactory extends Factory $licenseSeat->update([ 'assigned_to' => $target->id, - 'user_id' => 1, // not ideal but works + 'created_by' => 1, // not ideal but works ]); return [ diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index b1255baeee..4d6d20651c 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -36,7 +36,7 @@ class AssetFactory extends Factory 'status_id' => function () { return Statuslabel::where('name', 'Ready to Deploy')->first() ?? Statuslabel::factory()->rtd()->create(['name' => 'Ready to Deploy']); }, - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'asset_tag' => $this->faker->unixTime('now'), 'notes' => 'Created by DB seeder', 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'), diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index 6790897567..8acecd55d7 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -28,7 +28,7 @@ class AssetModelFactory extends Factory public function definition() { return [ - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'name' => $this->faker->catchPhrase(), 'category_id' => Category::factory(), 'model_number' => $this->faker->creditCardNumber(), diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index 2a89c12892..540dcb3085 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -29,7 +29,7 @@ class CategoryFactory extends Factory 'eula_text' => $this->faker->paragraph(), 'require_acceptance' => false, 'use_default_eula' => $this->faker->boolean(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), ]; } diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 607822fef1..5f1ac0c98a 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -23,6 +23,7 @@ class CompanyFactory extends Factory { return [ 'name' => $this->faker->unique()->company(), + 'created_by' => 1, ]; } } diff --git a/database/factories/ComponentFactory.php b/database/factories/ComponentFactory.php index 2557f29c77..51942fc694 100644 --- a/database/factories/ComponentFactory.php +++ b/database/factories/ComponentFactory.php @@ -108,7 +108,7 @@ class ComponentFactory extends Factory $component->assets()->attach($component->id, [ 'component_id' => $component->id, 'created_at' => Carbon::now(), - 'user_id' => 1, + 'created_by' => 1, 'asset_id' => $asset->id ?? Asset::factory()->create()->id, ]); }); diff --git a/database/factories/ConsumableFactory.php b/database/factories/ConsumableFactory.php index ca3a2faf95..4a4b3ef872 100644 --- a/database/factories/ConsumableFactory.php +++ b/database/factories/ConsumableFactory.php @@ -30,7 +30,7 @@ class ConsumableFactory extends Factory return [ 'name' => $this->faker->words(3, true), 'category_id' => Category::factory(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'item_no' => $this->faker->numberBetween(1000000, 50000000), 'order_number' => $this->faker->numberBetween(1000000, 50000000), 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'), @@ -104,7 +104,7 @@ class ConsumableFactory extends Factory $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, - 'user_id' => $user->id, + 'created_by' => $user->id, 'assigned_to' => $user->id, 'note' => '', ]); @@ -124,7 +124,7 @@ class ConsumableFactory extends Factory $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, 'created_at' => Carbon::now(), - 'user_id' => User::factory()->create()->id, + 'created_by' => User::factory()->create()->id, 'assigned_to' => $user->id ?? User::factory()->create()->id, ]); }); diff --git a/database/factories/DepartmentFactory.php b/database/factories/DepartmentFactory.php index afcc9cbd33..011a632669 100644 --- a/database/factories/DepartmentFactory.php +++ b/database/factories/DepartmentFactory.php @@ -25,7 +25,7 @@ class DepartmentFactory extends Factory { return [ 'name' => $this->faker->unique()->word() . ' Department', - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'location_id' => Location::factory(), ]; } diff --git a/database/factories/DepreciationFactory.php b/database/factories/DepreciationFactory.php index 6359e2326b..52258e784b 100644 --- a/database/factories/DepreciationFactory.php +++ b/database/factories/DepreciationFactory.php @@ -24,7 +24,7 @@ class DepreciationFactory extends Factory { return [ 'name' => $this->faker->unique()->catchPhrase(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'months' => 36, ]; } diff --git a/database/factories/ImportFactory.php b/database/factories/ImportFactory.php new file mode 100644 index 0000000000..0b0f79aa44 --- /dev/null +++ b/database/factories/ImportFactory.php @@ -0,0 +1,146 @@ + + */ +class ImportFactory extends Factory +{ + /** + * @inheritdoc + */ + protected $model = Import::class; + + /** + * @inheritdoc + */ + public function definition() + { + return [ + 'name' => $this->faker->company, + 'file_path' => Str::random().'.csv', + 'filesize' => $this->faker->randomDigitNotNull(), + 'field_map' => null, + ]; + } + + /** + * Create an accessory import type. + * + * @return static + */ + public function accessory() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\AccessoriesImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Accessories"; + $attributes['import_type'] = 'accessory'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create an asset import type. + * + * @return static + */ + public function asset() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\AssetsImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Assets"; + $attributes['import_type'] = 'asset'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a component import type. + * + * @return static + */ + public function component() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\ComponentsImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Components"; + $attributes['import_type'] = 'component'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a consumable import type. + * + * @return static + */ + public function consumable() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\ConsumablesImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Consumables"; + $attributes['import_type'] = 'consumable'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a license import type. + * + * @return static + */ + public function license() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\LicensesImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Licenses"; + $attributes['import_type'] = 'license'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a users import type. + * + * @return static + */ + public function users() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\UsersImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Employees"; + $attributes['import_type'] = 'user'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } +} diff --git a/database/factories/LicenseFactory.php b/database/factories/LicenseFactory.php index 6360735c5f..1f5b105f42 100644 --- a/database/factories/LicenseFactory.php +++ b/database/factories/LicenseFactory.php @@ -25,7 +25,7 @@ class LicenseFactory extends Factory public function definition() { return [ - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'name' => $this->faker->name(), 'license_email' => $this->faker->safeEmail(), 'serial' => $this->faker->uuid(), diff --git a/database/factories/ManufacturerFactory.php b/database/factories/ManufacturerFactory.php index 7d6892426d..47d4f672f3 100644 --- a/database/factories/ManufacturerFactory.php +++ b/database/factories/ManufacturerFactory.php @@ -24,7 +24,7 @@ class ManufacturerFactory extends Factory { return [ 'name' => $this->faker->unique()->company(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'support_phone' => $this->faker->phoneNumber(), 'url' => $this->faker->url(), 'support_email' => $this->faker->safeEmail(), diff --git a/database/factories/PredefinedKitFactory.php b/database/factories/PredefinedKitFactory.php new file mode 100644 index 0000000000..32e192655f --- /dev/null +++ b/database/factories/PredefinedKitFactory.php @@ -0,0 +1,23 @@ + + */ +class PredefinedKitFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => $this->faker->words(3, true), + ]; + } +} diff --git a/database/factories/StatuslabelFactory.php b/database/factories/StatuslabelFactory.php index fa2e5d5e1a..1f04f4564e 100644 --- a/database/factories/StatuslabelFactory.php +++ b/database/factories/StatuslabelFactory.php @@ -26,7 +26,7 @@ class StatuslabelFactory extends Factory 'name' => $this->faker->sentence(), 'created_at' => $this->faker->dateTime(), 'updated_at' => $this->faker->dateTime(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'deleted_at' => null, 'deployable' => 0, 'pending' => 0, diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 746d88a589..4b752b736f 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -7,6 +7,9 @@ use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use \Auth; +/** + * @extends Factory + */ class UserFactory extends Factory { /** @@ -35,6 +38,7 @@ class UserFactory extends Factory 'state' => $this->faker->stateAbbr(), 'username' => $this->faker->unique()->username(), 'zip' => $this->faker->postcode(), + 'created_by' => 1, ]; } @@ -141,6 +145,11 @@ class UserFactory extends Factory return $this->appendPermission(['assets.view.requestable' => '1']); } + public function deleteAssetModels() + { + return $this->appendPermission(['models.delete' => '1']); + } + public function viewAccessories() { return $this->appendPermission(['accessories.view' => '1']); @@ -201,6 +210,11 @@ class UserFactory extends Factory return $this->appendPermission(['consumables.checkout' => '1']); } + public function deleteDepartments() + { + return $this->appendPermission(['departments.delete' => '1']); + } + public function viewDepartments() { return $this->appendPermission(['departments.view' => '1']); @@ -241,11 +255,6 @@ class UserFactory extends Factory return $this->appendPermission(['components.view' => '1']); } - public function createCompanies() - { - return $this->appendPermission(['companies.create' => '1']); - } - public function createComponents() { return $this->appendPermission(['components.create' => '1']); @@ -271,6 +280,16 @@ class UserFactory extends Factory return $this->appendPermission(['components.checkout' => '1']); } + public function createCompanies() + { + return $this->appendPermission(['companies.create' => '1']); + } + + public function deleteCompanies() + { + return $this->appendPermission(['companies.delete' => '1']); + } + public function viewUsers() { return $this->appendPermission(['users.view' => '1']); @@ -291,6 +310,16 @@ class UserFactory extends Factory return $this->appendPermission(['users.delete' => '1']); } + public function deleteCategories() + { + return $this->appendPermission(['categories.delete' => '1']); + } + + public function deleteLocations() + { + return $this->appendPermission(['locations.delete' => '1']); + } + public function canEditOwnLocation() { return $this->appendPermission(['self.edit_location' => '1']); @@ -306,6 +335,41 @@ class UserFactory extends Factory return $this->appendPermission(['import' => '1']); } + public function deleteCustomFields() + { + return $this->appendPermission(['customfields.delete' => '1']); + } + + public function deleteCustomFieldsets() + { + return $this->appendPermission(['customfields.delete' => '1']); + } + + public function deleteDepreciations() + { + return $this->appendPermission(['depreciations.delete' => '1']); + } + + public function deleteManufacturers() + { + return $this->appendPermission(['manufacturers.delete' => '1']); + } + + public function deletePredefinedKits() + { + return $this->appendPermission(['kits.delete' => '1']); + } + + public function deleteStatusLabels() + { + return $this->appendPermission(['statuslabels.delete' => '1']); + } + + public function deleteSuppliers() + { + return $this->appendPermission(['suppliers.delete' => '1']); + } + private function appendPermission(array $permission) { return $this->state(function ($currentState) use ($permission) { diff --git a/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php b/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php new file mode 100644 index 0000000000..a57406ce10 --- /dev/null +++ b/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php @@ -0,0 +1,93 @@ +add_to_table_list() as $add_table) { + if (!Schema::hasColumn($add_table, 'created_by')) { + Schema::table($add_table, function (Blueprint $add_table) { + $add_table->unsignedBigInteger('created_by')->nullable()->before('created_at'); + }); + } + } + + foreach ($this->existing_table_list() as $table) { + if (Schema::hasColumn($table, 'user_id')) { + Schema::table($table, function (Blueprint $table) { + $table->renameColumn('user_id', 'created_by'); + }); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + foreach ($this->add_to_table_list() as $add_table) { + if (Schema::hasColumn($add_table, 'created_by')) { + Schema::table($add_table, function (Blueprint $add_table) { + $add_table->dropColumn('created_by'); + }); + } + } + + foreach ($this->existing_table_list() as $table) { + if (Schema::hasColumn($table, 'user_id')) { + Schema::table($table, function (Blueprint $table) { + $table->renameColumn('created_by', 'user_id'); + }); + } + } + } + + public function existing_table_list() { + return [ + 'accessories', + 'accessories_checkout', + 'action_logs', + 'asset_maintenances', + 'assets', + 'categories', + 'components', + 'components_assets', + 'consumables', + 'consumables_users', + 'custom_fields', + 'custom_fieldsets', + 'departments', + 'depreciations', + 'license_seats', + 'licenses', + 'locations', + 'manufacturers', + 'models', + 'settings', + 'status_labels', + 'suppliers', + 'users', + ]; + } + + public function add_to_table_list() { + return [ + 'companies', + 'imports', + 'kits', + 'kits_accessories', + 'kits_consumables', + 'kits_licenses', + 'kits_models', + 'users_groups', + ]; + } +}; diff --git a/database/seeders/AccessorySeeder.php b/database/seeders/AccessorySeeder.php index 2330a99733..5f4cca8cf6 100644 --- a/database/seeders/AccessorySeeder.php +++ b/database/seeders/AccessorySeeder.php @@ -35,25 +35,25 @@ class AccessorySeeder extends Seeder Accessory::factory()->appleUsbKeyboard()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Accessory::factory()->appleBtKeyboard()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Accessory::factory()->appleMouse()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Accessory::factory()->microsoftMouse()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); diff --git a/database/seeders/ActionlogSeeder.php b/database/seeders/ActionlogSeeder.php index 28191d53b0..3903980007 100644 --- a/database/seeders/ActionlogSeeder.php +++ b/database/seeders/ActionlogSeeder.php @@ -27,16 +27,16 @@ class ActionlogSeeder extends Seeder Actionlog::factory() ->count(300) ->assetCheckoutToUser() - ->create(['user_id' => $admin->id]); + ->create(['created_by' => $admin->id]); Actionlog::factory() ->count(100) ->assetCheckoutToLocation() - ->create(['user_id' => $admin->id]); + ->create(['created_by' => $admin->id]); Actionlog::factory() ->count(20) ->licenseCheckoutToUser() - ->create(['user_id' => $admin->id]); + ->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/AssetModelSeeder.php b/database/seeders/AssetModelSeeder.php index 1fc0b28cd3..f2902ffe7c 100755 --- a/database/seeders/AssetModelSeeder.php +++ b/database/seeders/AssetModelSeeder.php @@ -17,34 +17,34 @@ class AssetModelSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); // Laptops - AssetModel::factory()->count(1)->mbp13Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->mbpAirModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->surfaceModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->xps13Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->spectreModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->zenbookModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->yogaModel()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->mbp13Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->mbpAirModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->surfaceModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->xps13Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->spectreModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->zenbookModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->yogaModel()->create(['created_by' => $admin->id]); // Desktops - AssetModel::factory()->count(1)->macproModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->lenovoI5Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->optiplexModel()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->macproModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->lenovoI5Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->optiplexModel()->create(['created_by' => $admin->id]); // Conference Phones - AssetModel::factory()->count(1)->polycomModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->polycomcxModel()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->polycomModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->polycomcxModel()->create(['created_by' => $admin->id]); // Tablets - AssetModel::factory()->count(1)->ipadModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->tab3Model()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->ipadModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->tab3Model()->create(['created_by' => $admin->id]); // Phones - AssetModel::factory()->count(1)->iphone11Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->iphone12Model()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->iphone11Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->iphone12Model()->create(['created_by' => $admin->id]); // Displays - AssetModel::factory()->count(1)->ultrafine()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->ultrasharp()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->ultrafine()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->ultrasharp()->create(['created_by' => $admin->id]); $src = public_path('/img/demo/models/'); $dst = 'models'.'/'; diff --git a/database/seeders/AssetSeeder.php b/database/seeders/AssetSeeder.php index 5fdc09bdb3..9d21e7f9fa 100644 --- a/database/seeders/AssetSeeder.php +++ b/database/seeders/AssetSeeder.php @@ -25,7 +25,7 @@ class AssetSeeder extends Seeder $this->ensureLocationsSeeded(); $this->ensureSuppliersSeeded(); - $this->admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); + $this->adminuser = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); $this->locationIds = Location::all()->pluck('id'); $this->supplierIds = Supplier::all()->pluck('id'); @@ -82,7 +82,7 @@ class AssetSeeder extends Seeder return fn($sequence) => [ 'rtd_location_id' => $this->locationIds->random(), 'supplier_id' => $this->supplierIds->random(), - 'user_id' => $this->admin->id, + 'created_by' => $this->adminuser->id, ]; } } diff --git a/database/seeders/CategorySeeder.php b/database/seeders/CategorySeeder.php index da542cff9e..137dea2aba 100755 --- a/database/seeders/CategorySeeder.php +++ b/database/seeders/CategorySeeder.php @@ -14,20 +14,20 @@ class CategorySeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Category::factory()->count(1)->assetLaptopCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetDesktopCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetTabletCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetMobileCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetDisplayCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetVoipCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetConferenceCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->accessoryKeyboardCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->accessoryMouseCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->consumablePaperCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->consumableInkCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->componentHddCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->componentRamCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->licenseGraphicsCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->licenseOfficeCategory()->create(['user_id' => $admin->id]); + Category::factory()->count(1)->assetLaptopCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetDesktopCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetTabletCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetMobileCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetDisplayCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetVoipCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetConferenceCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->accessoryKeyboardCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->accessoryMouseCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->consumablePaperCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->consumableInkCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->componentHddCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->componentRamCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->licenseGraphicsCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->licenseOfficeCategory()->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/ConsumableSeeder.php b/database/seeders/ConsumableSeeder.php index 42527e1df8..de20141c7a 100644 --- a/database/seeders/ConsumableSeeder.php +++ b/database/seeders/ConsumableSeeder.php @@ -16,8 +16,8 @@ class ConsumableSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Consumable::factory()->count(1)->cardstock()->create(['user_id' => $admin->id]); - Consumable::factory()->count(1)->paper()->create(['user_id' => $admin->id]); - Consumable::factory()->count(1)->ink()->create(['user_id' => $admin->id]); + Consumable::factory()->count(1)->cardstock()->create(['created_by' => $admin->id]); + Consumable::factory()->count(1)->paper()->create(['created_by' => $admin->id]); + Consumable::factory()->count(1)->ink()->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/DepartmentSeeder.php b/database/seeders/DepartmentSeeder.php index 7406b97afb..7f20ee8cb9 100644 --- a/database/seeders/DepartmentSeeder.php +++ b/database/seeders/DepartmentSeeder.php @@ -23,32 +23,32 @@ class DepartmentSeeder extends Seeder Department::factory()->count(1)->hr()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->engineering()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->marketing()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->client()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->product()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->silly()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); } } diff --git a/database/seeders/DepreciationSeeder.php b/database/seeders/DepreciationSeeder.php index 349d8aff53..ed78c0b115 100644 --- a/database/seeders/DepreciationSeeder.php +++ b/database/seeders/DepreciationSeeder.php @@ -14,8 +14,8 @@ class DepreciationSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Depreciation::factory()->count(1)->computer()->create(['user_id' => $admin->id]); - Depreciation::factory()->count(1)->display()->create(['user_id' => $admin->id]); - Depreciation::factory()->count(1)->mobilePhones()->create(['user_id' => $admin->id]); + Depreciation::factory()->count(1)->computer()->create(['created_by' => $admin->id]); + Depreciation::factory()->count(1)->display()->create(['created_by' => $admin->id]); + Depreciation::factory()->count(1)->mobilePhones()->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/LicenseSeeder.php b/database/seeders/LicenseSeeder.php index 4868dd41e1..bc19727f7e 100644 --- a/database/seeders/LicenseSeeder.php +++ b/database/seeders/LicenseSeeder.php @@ -33,25 +33,25 @@ class LicenseSeeder extends Seeder License::factory()->count(1)->photoshop()->create([ 'category_id' => $categoryIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); License::factory()->count(1)->acrobat()->create([ 'category_id' => $categoryIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); License::factory()->count(1)->indesign()->create([ 'category_id' => $categoryIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); License::factory()->count(1)->office()->create([ 'category_id' => $categoryIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); } } diff --git a/database/seeders/ManufacturerSeeder.php b/database/seeders/ManufacturerSeeder.php index cbd70f4c3d..adc13dc73e 100644 --- a/database/seeders/ManufacturerSeeder.php +++ b/database/seeders/ManufacturerSeeder.php @@ -16,17 +16,17 @@ class ManufacturerSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Manufacturer::factory()->count(1)->apple()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->microsoft()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->dell()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->asus()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->hp()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->lenovo()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->lg()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->polycom()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->adobe()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->avery()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->crucial()->create(['user_id' => $admin->id]); + Manufacturer::factory()->count(1)->apple()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->microsoft()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->dell()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->asus()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->hp()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->lenovo()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->lg()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->polycom()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->adobe()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->avery()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->crucial()->create(['created_by' => $admin->id]); $src = public_path('/img/demo/manufacturers/'); $dst = 'manufacturers'.'/'; diff --git a/database/seeders/StatuslabelSeeder.php b/database/seeders/StatuslabelSeeder.php index fbc6a9fb66..be36e7790d 100755 --- a/database/seeders/StatuslabelSeeder.php +++ b/database/seeders/StatuslabelSeeder.php @@ -16,22 +16,22 @@ class StatuslabelSeeder extends Seeder Statuslabel::factory()->rtd()->create([ 'name' => 'Ready to Deploy', - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Statuslabel::factory()->pending()->create([ 'name' => 'Pending', - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Statuslabel::factory()->archived()->create([ 'name' => 'Archived', - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); - Statuslabel::factory()->outForDiagnostics()->create(['user_id' => $admin->id]); - Statuslabel::factory()->outForRepair()->create(['user_id' => $admin->id]); - Statuslabel::factory()->broken()->create(['user_id' => $admin->id]); - Statuslabel::factory()->lost()->create(['user_id' => $admin->id]); + Statuslabel::factory()->outForDiagnostics()->create(['created_by' => $admin->id]); + Statuslabel::factory()->outForRepair()->create(['created_by' => $admin->id]); + Statuslabel::factory()->broken()->create(['created_by' => $admin->id]); + Statuslabel::factory()->lost()->create(['created_by' => $admin->id]); } } diff --git a/package-lock.json b/package-lock.json index a5296ae5f3..5a98e56c6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "jquery-ui": "^1.14.0", "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.2", + "jspdf-autotable": "^3.8.3", "less": "^4.2.0", "less-loader": "^6.0", "list.js": "^1.5.0", @@ -7167,8 +7167,9 @@ } }, "node_modules/jspdf-autotable": { - "version": "3.8.2", - "license": "MIT", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.3.tgz", + "integrity": "sha512-PQFdljBt+ijm6ZWXYxhZ54A/awV63UKcipYoA2+YGsz0BXXiXTIL/FIg+V30j7wPdSdzClfbB3qKX9UeuFylPQ==", "peerDependencies": { "jspdf": "^2.5.1" } diff --git a/package.json b/package.json index 3d8e3eda2d..2b3ec19b63 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "jquery-ui": "^1.14.0", "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.2", + "jspdf-autotable": "^3.8.3", "less": "^4.2.0", "less-loader": "^6.0", "list.js": "^1.5.0", diff --git a/public/css/build/app.css b/public/css/build/app.css index a2e11fc496..d35eebb0a6 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 807ea0b0a4..6e4825dc40 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 cafb9a2c4a..3d3cc4f98f 100644 Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ diff --git a/public/js/build/app.js.LICENSE.txt b/public/js/build/app.js.LICENSE.txt index cb3342c839..931b6a9882 100644 Binary files a/public/js/build/app.js.LICENSE.txt and b/public/js/build/app.js.LICENSE.txt differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 822ca188e5..291f4f95fe 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -2,8 +2,8 @@ "/js/build/app.js": "/js/build/app.js?id=5e9ac5c1a7e089f056fb1dba566193a6", "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=f0b08873a06bb54daeee176a9459f4a9", "/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=f4397c717b99fce41a633ca6edd5d1f4", - "/css/build/overrides.css": "/css/build/overrides.css?id=7fa231e10a862be5469f803bb9b02e16", - "/css/build/app.css": "/css/build/app.css?id=6f058a6824a8b236f9e5ac4c993d4520", + "/css/build/overrides.css": "/css/build/overrides.css?id=1c3ffc5fb379e21523f2a9b03f986edb", + "/css/build/app.css": "/css/build/app.css?id=d04f32982fb319ac35a32d362089f18b", "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=4ea0068716c1bb2434d87a16d51b98c9", "/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690", "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=393aaa7b368b0670fc42434c8cca7dc7", @@ -19,7 +19,7 @@ "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374", "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=0640e45bad692dcf62873c6e85904899", "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb", - "/css/dist/all.css": "/css/dist/all.css?id=0665a8b7a6fcf6d93218ffdf67e98193", + "/css/dist/all.css": "/css/dist/all.css?id=9f69886d7a8e4c383cd09a48573922b7", "/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", diff --git a/public/vendor/livewire/livewire.esm.js b/public/vendor/livewire/livewire.esm.js index 0f6daa54ec..ae4bb2cb78 100644 --- a/public/vendor/livewire/livewire.esm.js +++ b/public/vendor/livewire/livewire.esm.js @@ -1432,10 +1432,10 @@ var require_module_cjs = __commonJS({ }); } function cleanupElement(el) { - if (el._x_cleanups) { - while (el._x_cleanups.length) - el._x_cleanups.pop()(); - } + var _a, _b; + (_a = el._x_effects) == null ? void 0 : _a.forEach(dequeueJob); + while ((_b = el._x_cleanups) == null ? void 0 : _b.length) + el._x_cleanups.pop()(); } var observer = new MutationObserver(onMutate); var currentlyObserving = false; @@ -1673,27 +1673,23 @@ var require_module_cjs = __commonJS({ magics[name] = callback; } function injectMagics(obj, el) { + let memoizedUtilities = getUtilities(el); Object.entries(magics).forEach(([name, callback]) => { - let memoizedUtilities = null; - function getUtilities() { - if (memoizedUtilities) { - return memoizedUtilities; - } else { - let [utilities, cleanup] = getElementBoundUtilities(el); - memoizedUtilities = { interceptor, ...utilities }; - onElRemoved(el, cleanup); - return memoizedUtilities; - } - } Object.defineProperty(obj, `$${name}`, { get() { - return callback(el, getUtilities()); + return callback(el, memoizedUtilities); }, enumerable: false }); }); return obj; } + function getUtilities(el) { + let [utilities, cleanup] = getElementBoundUtilities(el); + let utils = { interceptor, ...utilities }; + onElRemoved(el, cleanup); + return utils; + } function tryCatch(el, expression, callback, ...args) { try { return callback(...args); @@ -2067,8 +2063,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } function destroyTree(root, walker = walk) { walker(root, (el) => { - cleanupAttributes(el); cleanupElement(el); + cleanupAttributes(el); }); } function warnAboutMissingPlugins() { @@ -2648,34 +2644,37 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } return rawValue ? Boolean(rawValue) : null; } + var booleanAttributes = /* @__PURE__ */ new Set([ + "allowfullscreen", + "async", + "autofocus", + "autoplay", + "checked", + "controls", + "default", + "defer", + "disabled", + "formnovalidate", + "inert", + "ismap", + "itemscope", + "loop", + "multiple", + "muted", + "nomodule", + "novalidate", + "open", + "playsinline", + "readonly", + "required", + "reversed", + "selected", + "shadowrootclonable", + "shadowrootdelegatesfocus", + "shadowrootserializable" + ]); function isBooleanAttr(attrName) { - const booleanAttributes = [ - "disabled", - "checked", - "required", - "readonly", - "open", - "selected", - "autofocus", - "itemscope", - "multiple", - "novalidate", - "allowfullscreen", - "allowpaymentrequest", - "formnovalidate", - "autoplay", - "controls", - "loop", - "muted", - "playsinline", - "default", - "ismap", - "reversed", - "async", - "defer", - "nomodule" - ]; - return booleanAttributes.includes(attrName); + return booleanAttributes.has(attrName); } function attributeShouldntBePreservedIfFalsy(name) { return !["aria-pressed", "aria-checked", "aria-expanded", "aria-selected"].includes(name); @@ -2776,10 +2775,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); return stores[name]; } stores[name] = value; + initInterceptors(stores[name]); if (typeof value === "object" && value !== null && value.hasOwnProperty("init") && typeof value.init === "function") { stores[name].init(); } - initInterceptors(stores[name]); } function getStores() { return stores; @@ -3070,7 +3069,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); placeInDom(el._x_teleport, target2, modifiers); }); }; - cleanup(() => clone2.remove()); + cleanup(() => mutateDom(() => { + clone2.remove(); + destroyTree(clone2); + })); }); var teleportContainerDuringClone = document.createElement("div"); function getTarget(expression) { @@ -3558,7 +3560,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); el._x_lookup = {}; effect3(() => loop(el, iteratorNames, evaluateItems, evaluateKey)); cleanup(() => { - Object.values(el._x_lookup).forEach((el2) => el2.remove()); + Object.values(el._x_lookup).forEach((el2) => mutateDom(() => { + destroyTree(el2); + el2.remove(); + })); delete el._x_prevKeys; delete el._x_lookup; }); @@ -3627,11 +3632,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } for (let i = 0; i < removes.length; i++) { let key = removes[i]; - if (!!lookup[key]._x_effects) { - lookup[key]._x_effects.forEach(dequeueJob); - } - lookup[key].remove(); - lookup[key] = null; + if (!(key in lookup)) + continue; + mutateDom(() => { + destroyTree(lookup[key]); + lookup[key].remove(); + }); delete lookup[key]; } for (let i = 0; i < moves.length; i++) { @@ -3752,12 +3758,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); }); el._x_currentIfEl = clone2; el._x_undoIf = () => { - walk(clone2, (node) => { - if (!!node._x_effects) { - node._x_effects.forEach(dequeueJob); - } + mutateDom(() => { + destroyTree(clone2); + clone2.remove(); }); - clone2.remove(); delete el._x_currentIfEl; }; return clone2; @@ -3812,9 +3816,9 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } }); -// ../alpine/packages/collapse/dist/module.cjs.js +// ../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js var require_module_cjs2 = __commonJS({ - "../alpine/packages/collapse/dist/module.cjs.js"(exports, module) { + "../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropNames2 = Object.getOwnPropertyNames; @@ -3887,7 +3891,7 @@ var require_module_cjs2 = __commonJS({ start: { height: current + "px" }, end: { height: full + "px" } }, () => el._x_isShown = true, () => { - if (Math.abs(el.getBoundingClientRect().height - full) < 1) { + if (el.getBoundingClientRect().height == full) { el.style.overflow = null; } }); @@ -3933,9 +3937,9 @@ var require_module_cjs2 = __commonJS({ } }); -// ../alpine/packages/focus/dist/module.cjs.js +// ../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js var require_module_cjs3 = __commonJS({ - "../alpine/packages/focus/dist/module.cjs.js"(exports, module) { + "../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js"(exports, module) { var __create2 = Object.create; var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; @@ -4935,9 +4939,9 @@ var require_module_cjs3 = __commonJS({ } }); -// ../alpine/packages/persist/dist/module.cjs.js +// ../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js var require_module_cjs4 = __commonJS({ - "../alpine/packages/persist/dist/module.cjs.js"(exports, module) { + "../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropNames2 = Object.getOwnPropertyNames; @@ -5024,9 +5028,9 @@ var require_module_cjs4 = __commonJS({ } }); -// ../alpine/packages/intersect/dist/module.cjs.js +// ../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js var require_module_cjs5 = __commonJS({ - "../alpine/packages/intersect/dist/module.cjs.js"(exports, module) { + "../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropNames2 = Object.getOwnPropertyNames; @@ -5106,8 +5110,80 @@ var require_module_cjs5 = __commonJS({ } }); -// ../alpine/packages/anchor/dist/module.cjs.js +// node_modules/@alpinejs/resize/dist/module.cjs.js var require_module_cjs6 = __commonJS({ + "node_modules/@alpinejs/resize/dist/module.cjs.js"(exports, module) { + var __defProp2 = Object.defineProperty; + var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; + var __getOwnPropNames2 = Object.getOwnPropertyNames; + var __hasOwnProp2 = Object.prototype.hasOwnProperty; + var __export = (target, all2) => { + for (var name in all2) + __defProp2(target, name, { get: all2[name], enumerable: true }); + }; + var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); + var module_exports = {}; + __export(module_exports, { + default: () => module_default, + resize: () => src_default + }); + module.exports = __toCommonJS(module_exports); + function src_default(Alpine19) { + Alpine19.directive("resize", Alpine19.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => { + let evaluator = evaluateLater(expression); + let evaluate = (width, height) => { + evaluator(() => { + }, { scope: { "$width": width, "$height": height } }); + }; + let off = modifiers.includes("document") ? onDocumentResize(evaluate) : onElResize(el, evaluate); + cleanup(() => off()); + })); + } + function onElResize(el, callback) { + let observer = new ResizeObserver((entries) => { + let [width, height] = dimensions(entries); + callback(width, height); + }); + observer.observe(el); + return () => observer.disconnect(); + } + var documentResizeObserver; + var documentResizeObserverCallbacks = /* @__PURE__ */ new Set(); + function onDocumentResize(callback) { + documentResizeObserverCallbacks.add(callback); + if (!documentResizeObserver) { + documentResizeObserver = new ResizeObserver((entries) => { + let [width, height] = dimensions(entries); + documentResizeObserverCallbacks.forEach((i) => i(width, height)); + }); + documentResizeObserver.observe(document.documentElement); + } + return () => { + documentResizeObserverCallbacks.delete(callback); + }; + } + function dimensions(entries) { + let width, height; + for (let entry of entries) { + width = entry.borderBoxSize[0].inlineSize; + height = entry.borderBoxSize[0].blockSize; + } + return [width, height]; + } + var module_default = src_default; + } +}); + +// ../alpine/packages/anchor/dist/module.cjs.js +var require_module_cjs7 = __commonJS({ "../alpine/packages/anchor/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; @@ -6645,7 +6721,7 @@ var require_nprogress = __commonJS({ }); // ../alpine/packages/morph/dist/module.cjs.js -var require_module_cjs7 = __commonJS({ +var require_module_cjs8 = __commonJS({ "../alpine/packages/morph/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; @@ -6744,6 +6820,8 @@ var require_module_cjs7 = __commonJS({ let toAttributes = Array.from(to.attributes); for (let i = domAttributes.length - 1; i >= 0; i--) { let name = domAttributes[i].name; + if (name === "style") + continue; if (!to.hasAttribute(name)) { from2.removeAttribute(name); } @@ -6751,6 +6829,8 @@ var require_module_cjs7 = __commonJS({ for (let i = toAttributes.length - 1; i >= 0; i--) { let name = toAttributes[i].name; let value = toAttributes[i].value; + if (name === "style") + continue; if (from2.getAttribute(name) !== value) { from2.setAttribute(name, value); } @@ -7006,9 +7086,9 @@ var require_module_cjs7 = __commonJS({ } }); -// ../alpine/packages/mask/dist/module.cjs.js -var require_module_cjs8 = __commonJS({ - "../alpine/packages/mask/dist/module.cjs.js"(exports, module) { +// ../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js +var require_module_cjs9 = __commonJS({ + "../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropNames2 = Object.getOwnPropertyNames; @@ -8509,7 +8589,8 @@ var import_collapse = __toESM(require_module_cjs2()); var import_focus = __toESM(require_module_cjs3()); var import_persist2 = __toESM(require_module_cjs4()); var import_intersect = __toESM(require_module_cjs5()); -var import_anchor = __toESM(require_module_cjs6()); +var import_resize = __toESM(require_module_cjs6()); +var import_anchor = __toESM(require_module_cjs7()); // js/plugins/navigate/history.js var Snapshot = class { @@ -8660,7 +8741,7 @@ function extractDestinationFromLink(linkEl) { return createUrlObjectFromString(linkEl.getAttribute("href")); } function createUrlObjectFromString(urlString) { - return new URL(urlString, document.baseURI); + return urlString !== null && new URL(urlString, document.baseURI); } function getUriStringFromUrlObject(urlObject) { return urlObject.pathname + urlObject.search + urlObject.hash; @@ -8782,8 +8863,10 @@ function restoreScrollPositionOrScrollToTop() { } }; queueMicrotask(() => { - scroll(document.body); - document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll); + queueMicrotask(() => { + scroll(document.body); + document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll); + }); }); } @@ -9087,12 +9170,16 @@ function navigate_default(Alpine19) { let shouldPrefetchOnHover = modifiers.includes("hover"); shouldPrefetchOnHover && whenThisLinkIsHoveredFor(el, 60, () => { let destination = extractDestinationFromLink(el); + if (!destination) + return; prefetchHtml(destination, (html, finalDestination) => { storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination); }); }); whenThisLinkIsPressed(el, (whenItIsReleased) => { let destination = extractDestinationFromLink(el); + if (!destination) + return; prefetchHtml(destination, (html, finalDestination) => { storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination); }); @@ -9441,8 +9528,8 @@ function fromQueryString(search) { } // js/lifecycle.js -var import_morph = __toESM(require_module_cjs7()); -var import_mask = __toESM(require_module_cjs8()); +var import_morph = __toESM(require_module_cjs8()); +var import_mask = __toESM(require_module_cjs9()); var import_alpinejs5 = __toESM(require_module_cjs()); function start() { setTimeout(() => ensureLivewireScriptIsntMisplaced()); @@ -9451,6 +9538,7 @@ function start() { import_alpinejs5.default.plugin(import_morph.default); import_alpinejs5.default.plugin(history2); import_alpinejs5.default.plugin(import_intersect.default); + import_alpinejs5.default.plugin(import_resize.default); import_alpinejs5.default.plugin(import_collapse.default); import_alpinejs5.default.plugin(import_anchor.default); import_alpinejs5.default.plugin(import_focus.default); diff --git a/public/vendor/livewire/livewire.js b/public/vendor/livewire/livewire.js index 8eef48fadf..687180f6b1 100644 --- a/public/vendor/livewire/livewire.js +++ b/public/vendor/livewire/livewire.js @@ -851,10 +851,9 @@ }); } function cleanupElement(el) { - if (el._x_cleanups) { - while (el._x_cleanups.length) - el._x_cleanups.pop()(); - } + el._x_effects?.forEach(dequeueJob); + while (el._x_cleanups?.length) + el._x_cleanups.pop()(); } var observer = new MutationObserver(onMutate); var currentlyObserving = false; @@ -1092,27 +1091,23 @@ magics[name] = callback; } function injectMagics(obj, el) { + let memoizedUtilities = getUtilities(el); Object.entries(magics).forEach(([name, callback]) => { - let memoizedUtilities = null; - function getUtilities() { - if (memoizedUtilities) { - return memoizedUtilities; - } else { - let [utilities, cleanup2] = getElementBoundUtilities(el); - memoizedUtilities = { interceptor, ...utilities }; - onElRemoved(el, cleanup2); - return memoizedUtilities; - } - } Object.defineProperty(obj, `$${name}`, { get() { - return callback(el, getUtilities()); + return callback(el, memoizedUtilities); }, enumerable: false }); }); return obj; } + function getUtilities(el) { + let [utilities, cleanup2] = getElementBoundUtilities(el); + let utils = { interceptor, ...utilities }; + onElRemoved(el, cleanup2); + return utils; + } function tryCatch(el, expression, callback, ...args) { try { return callback(...args); @@ -1486,8 +1481,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } function destroyTree(root, walker = walk) { walker(root, (el) => { - cleanupAttributes(el); cleanupElement(el); + cleanupAttributes(el); }); } function warnAboutMissingPlugins() { @@ -2067,34 +2062,37 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } return rawValue ? Boolean(rawValue) : null; } + var booleanAttributes = /* @__PURE__ */ new Set([ + "allowfullscreen", + "async", + "autofocus", + "autoplay", + "checked", + "controls", + "default", + "defer", + "disabled", + "formnovalidate", + "inert", + "ismap", + "itemscope", + "loop", + "multiple", + "muted", + "nomodule", + "novalidate", + "open", + "playsinline", + "readonly", + "required", + "reversed", + "selected", + "shadowrootclonable", + "shadowrootdelegatesfocus", + "shadowrootserializable" + ]); function isBooleanAttr(attrName) { - const booleanAttributes = [ - "disabled", - "checked", - "required", - "readonly", - "open", - "selected", - "autofocus", - "itemscope", - "multiple", - "novalidate", - "allowfullscreen", - "allowpaymentrequest", - "formnovalidate", - "autoplay", - "controls", - "loop", - "muted", - "playsinline", - "default", - "ismap", - "reversed", - "async", - "defer", - "nomodule" - ]; - return booleanAttributes.includes(attrName); + return booleanAttributes.has(attrName); } function attributeShouldntBePreservedIfFalsy(name) { return !["aria-pressed", "aria-checked", "aria-expanded", "aria-selected"].includes(name); @@ -2195,10 +2193,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); return stores[name]; } stores[name] = value; + initInterceptors(stores[name]); if (typeof value === "object" && value !== null && value.hasOwnProperty("init") && typeof value.init === "function") { stores[name].init(); } - initInterceptors(stores[name]); } function getStores() { return stores; @@ -3136,7 +3134,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); placeInDom(el._x_teleport, target2, modifiers); }); }; - cleanup2(() => clone2.remove()); + cleanup2(() => mutateDom(() => { + clone2.remove(); + destroyTree(clone2); + })); }); var teleportContainerDuringClone = document.createElement("div"); function getTarget(expression) { @@ -3624,7 +3625,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); el._x_lookup = {}; effect3(() => loop(el, iteratorNames, evaluateItems, evaluateKey)); cleanup2(() => { - Object.values(el._x_lookup).forEach((el2) => el2.remove()); + Object.values(el._x_lookup).forEach((el2) => mutateDom(() => { + destroyTree(el2); + el2.remove(); + })); delete el._x_prevKeys; delete el._x_lookup; }); @@ -3693,11 +3697,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } for (let i = 0; i < removes.length; i++) { let key = removes[i]; - if (!!lookup[key]._x_effects) { - lookup[key]._x_effects.forEach(dequeueJob); - } - lookup[key].remove(); - lookup[key] = null; + if (!(key in lookup)) + continue; + mutateDom(() => { + destroyTree(lookup[key]); + lookup[key].remove(); + }); delete lookup[key]; } for (let i = 0; i < moves.length; i++) { @@ -3818,12 +3823,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); }); el._x_currentIfEl = clone2; el._x_undoIf = () => { - walk(clone2, (node) => { - if (!!node._x_effects) { - node._x_effects.forEach(dequeueJob); - } + mutateDom(() => { + destroyTree(clone2); + clone2.remove(); }); - clone2.remove(); delete el._x_currentIfEl; }; return clone2; @@ -4762,7 +4765,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } }; - // ../alpine/packages/collapse/dist/module.esm.js + // ../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.esm.js function src_default2(Alpine3) { Alpine3.directive("collapse", collapse); collapse.inline = (el, { modifiers }) => { @@ -4812,7 +4815,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); start: { height: current + "px" }, end: { height: full + "px" } }, () => el._x_isShown = true, () => { - if (Math.abs(el.getBoundingClientRect().height - full) < 1) { + if (el.getBoundingClientRect().height == full) { el.style.overflow = null; } }); @@ -4856,7 +4859,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } var module_default2 = src_default2; - // ../alpine/packages/focus/dist/module.esm.js + // ../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.esm.js var candidateSelectors = ["input", "select", "textarea", "a[href]", "button", "[tabindex]:not(slot)", "audio[controls]", "video[controls]", '[contenteditable]:not([contenteditable="false"])', "details>summary:first-of-type", "details"]; var candidateSelector = /* @__PURE__ */ candidateSelectors.join(","); var NoElement = typeof Element === "undefined"; @@ -5805,7 +5808,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } var module_default3 = src_default3; - // ../alpine/packages/persist/dist/module.esm.js + // ../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.esm.js function src_default4(Alpine3) { let persist = () => { let alias; @@ -5867,7 +5870,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } var module_default4 = src_default4; - // ../alpine/packages/intersect/dist/module.esm.js + // ../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.esm.js function src_default5(Alpine3) { Alpine3.directive("intersect", Alpine3.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater: evaluateLater2, cleanup: cleanup2 }) => { let evaluate3 = evaluateLater2(expression); @@ -5922,6 +5925,51 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } var module_default5 = src_default5; + // node_modules/@alpinejs/resize/dist/module.esm.js + function src_default6(Alpine3) { + Alpine3.directive("resize", Alpine3.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater: evaluateLater2, cleanup: cleanup2 }) => { + let evaluator = evaluateLater2(expression); + let evaluate3 = (width, height) => { + evaluator(() => { + }, { scope: { "$width": width, "$height": height } }); + }; + let off = modifiers.includes("document") ? onDocumentResize(evaluate3) : onElResize(el, evaluate3); + cleanup2(() => off()); + })); + } + function onElResize(el, callback) { + let observer2 = new ResizeObserver((entries) => { + let [width, height] = dimensions(entries); + callback(width, height); + }); + observer2.observe(el); + return () => observer2.disconnect(); + } + var documentResizeObserver; + var documentResizeObserverCallbacks = /* @__PURE__ */ new Set(); + function onDocumentResize(callback) { + documentResizeObserverCallbacks.add(callback); + if (!documentResizeObserver) { + documentResizeObserver = new ResizeObserver((entries) => { + let [width, height] = dimensions(entries); + documentResizeObserverCallbacks.forEach((i) => i(width, height)); + }); + documentResizeObserver.observe(document.documentElement); + } + return () => { + documentResizeObserverCallbacks.delete(callback); + }; + } + function dimensions(entries) { + let width, height; + for (let entry of entries) { + width = entry.borderBoxSize[0].inlineSize; + height = entry.borderBoxSize[0].blockSize; + } + return [width, height]; + } + var module_default6 = src_default6; + // ../alpine/packages/anchor/dist/module.esm.js var min = Math.min; var max = Math.max; @@ -7096,7 +7144,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); platform: platformWithCache }); }; - function src_default6(Alpine3) { + function src_default7(Alpine3) { Alpine3.magic("anchor", (el) => { if (!el._x_anchor) throw "Alpine: No x-anchor directive found on element using $anchor..."; @@ -7154,7 +7202,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); let unstyled = modifiers.includes("no-style"); return { placement, offsetValue, unstyled }; } - var module_default6 = src_default6; + var module_default7 = src_default7; // js/plugins/navigate/history.js var Snapshot = class { @@ -7305,7 +7353,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); return createUrlObjectFromString(linkEl.getAttribute("href")); } function createUrlObjectFromString(urlString) { - return new URL(urlString, document.baseURI); + return urlString !== null && new URL(urlString, document.baseURI); } function getUriStringFromUrlObject(urlObject) { return urlObject.pathname + urlObject.search + urlObject.hash; @@ -7426,8 +7474,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); } }; queueMicrotask(() => { - scroll(document.body); - document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll); + queueMicrotask(() => { + scroll(document.body); + document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll); + }); }); } @@ -7730,12 +7780,16 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); let shouldPrefetchOnHover = modifiers.includes("hover"); shouldPrefetchOnHover && whenThisLinkIsHoveredFor(el, 60, () => { let destination = extractDestinationFromLink(el); + if (!destination) + return; prefetchHtml(destination, (html, finalDestination) => { storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination); }); }); whenThisLinkIsPressed(el, (whenItIsReleased) => { let destination = extractDestinationFromLink(el); + if (!destination) + return; prefetchHtml(destination, (html, finalDestination) => { storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination); }); @@ -8158,6 +8212,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); let toAttributes = Array.from(to.attributes); for (let i = domAttributes.length - 1; i >= 0; i--) { let name = domAttributes[i].name; + if (name === "style") + continue; if (!to.hasAttribute(name)) { from2.removeAttribute(name); } @@ -8165,6 +8221,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); for (let i = toAttributes.length - 1; i >= 0; i--) { let name = toAttributes[i].name; let value = toAttributes[i].value; + if (name === "style") + continue; if (from2.getAttribute(name) !== value) { from2.setAttribute(name, value); } @@ -8413,13 +8471,13 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); to.setAttribute("id", fromId); to.id = fromId; } - function src_default7(Alpine3) { + function src_default8(Alpine3) { Alpine3.morph = morph; } - var module_default7 = src_default7; + var module_default8 = src_default8; - // ../alpine/packages/mask/dist/module.esm.js - function src_default8(Alpine3) { + // ../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.esm.js + function src_default9(Alpine3) { Alpine3.directive("mask", (el, { value, expression }, { effect: effect3, evaluateLater: evaluateLater2, cleanup: cleanup2 }) => { let templateFn = () => expression; let lastInputValue = ""; @@ -8581,22 +8639,23 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); }); return template; } - var module_default8 = src_default8; + var module_default9 = src_default9; // js/lifecycle.js function start2() { setTimeout(() => ensureLivewireScriptIsntMisplaced()); dispatch(document, "livewire:init"); dispatch(document, "livewire:initializing"); - module_default.plugin(module_default7); + module_default.plugin(module_default8); module_default.plugin(history2); module_default.plugin(module_default5); - module_default.plugin(module_default2); module_default.plugin(module_default6); + module_default.plugin(module_default2); + module_default.plugin(module_default7); module_default.plugin(module_default3); module_default.plugin(module_default4); module_default.plugin(navigate_default); - module_default.plugin(module_default8); + module_default.plugin(module_default9); module_default.addRootSelector(() => "[wire\\:id]"); module_default.onAttributesAdded((el, attributes) => { if (!Array.from(attributes).some((attribute) => matchesForLivewireDirective(attribute.name))) diff --git a/public/vendor/livewire/livewire.min.js b/public/vendor/livewire/livewire.min.js index 36c4171c37..90a91971d8 100644 --- a/public/vendor/livewire/livewire.min.js +++ b/public/vendor/livewire/livewire.min.js @@ -1,14 +1,14 @@ -(()=>{var el=Object.create;var ti=Object.defineProperty;var tl=Object.getOwnPropertyDescriptor;var rl=Object.getOwnPropertyNames;var nl=Object.getPrototypeOf,il=Object.prototype.hasOwnProperty;var ol=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var sl=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of rl(t))!il.call(e,i)&&i!==r&&ti(e,i,{get:()=>t[i],enumerable:!(n=tl(t,i))||n.enumerable});return e};var al=(e,t,r)=>(r=e!=null?el(nl(e)):{},sl(t||!e||!e.__esModule?ti(r,"default",{value:e,enumerable:!0}):r,e));var ua=ol((Dn,la)=>{(function(e,t){typeof define=="function"&&define.amd?define(t):typeof Dn=="object"?la.exports=t():e.NProgress=t()})(Dn,function(){var e={};e.version="0.2.0";var t=e.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};e.configure=function(c){var d,m;for(d in c)m=c[d],m!==void 0&&c.hasOwnProperty(d)&&(t[d]=m);return this},e.status=null,e.set=function(c){var d=e.isStarted();c=r(c,t.minimum,1),e.status=c===1?null:c;var m=e.render(!d),b=m.querySelector(t.barSelector),g=t.speed,y=t.easing;return m.offsetWidth,o(function(v){t.positionUsing===""&&(t.positionUsing=e.getPositioningCSS()),s(b,i(c,g,y)),c===1?(s(m,{transition:"none",opacity:1}),m.offsetWidth,setTimeout(function(){s(m,{transition:"all "+g+"ms linear",opacity:0}),setTimeout(function(){e.remove(),v()},g)},g)):setTimeout(v,g)}),this},e.isStarted=function(){return typeof e.status=="number"},e.start=function(){e.status||e.set(0);var c=function(){setTimeout(function(){!e.status||(e.trickle(),c())},t.trickleSpeed)};return t.trickle&&c(),this},e.done=function(c){return!c&&!e.status?this:e.inc(.3+.5*Math.random()).set(1)},e.inc=function(c){var d=e.status;return d?(typeof c!="number"&&(c=(1-d)*r(Math.random()*d,.1,.95)),d=r(d+c,0,.994),e.set(d)):e.start()},e.trickle=function(){return e.inc(Math.random()*t.trickleRate)},function(){var c=0,d=0;e.promise=function(m){return!m||m.state()==="resolved"?this:(d===0&&e.start(),c++,d++,m.always(function(){d--,d===0?(c=0,e.done()):e.set((c-d)/c)}),this)}}(),e.render=function(c){if(e.isRendered())return document.getElementById("nprogress");l(document.documentElement,"nprogress-busy");var d=document.createElement("div");d.id="nprogress",d.innerHTML=t.template;var m=d.querySelector(t.barSelector),b=c?"-100":n(e.status||0),g=document.querySelector(t.parent),y;return s(m,{transition:"all 0 linear",transform:"translate3d("+b+"%,0,0)"}),t.showSpinner||(y=d.querySelector(t.spinnerSelector),y&&p(y)),g!=document.body&&l(g,"nprogress-custom-parent"),g.appendChild(d),d},e.remove=function(){u(document.documentElement,"nprogress-busy"),u(document.querySelector(t.parent),"nprogress-custom-parent");var c=document.getElementById("nprogress");c&&p(c)},e.isRendered=function(){return!!document.getElementById("nprogress")},e.getPositioningCSS=function(){var c=document.body.style,d="WebkitTransform"in c?"Webkit":"MozTransform"in c?"Moz":"msTransform"in c?"ms":"OTransform"in c?"O":"";return d+"Perspective"in c?"translate3d":d+"Transform"in c?"translate":"margin"};function r(c,d,m){return cm?m:c}function n(c){return(-1+c)*100}function i(c,d,m){var b;return t.positionUsing==="translate3d"?b={transform:"translate3d("+n(c)+"%,0,0)"}:t.positionUsing==="translate"?b={transform:"translate("+n(c)+"%,0)"}:b={"margin-left":n(c)+"%"},b.transition="all "+d+"ms "+m,b}var o=function(){var c=[];function d(){var m=c.shift();m&&m(d)}return function(m){c.push(m),c.length==1&&d()}}(),s=function(){var c=["Webkit","O","Moz","ms"],d={};function m(v){return v.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(_,T){return T.toUpperCase()})}function b(v){var _=document.body.style;if(v in _)return v;for(var T=c.length,A=v.charAt(0).toUpperCase()+v.slice(1),w;T--;)if(w=c[T]+A,w in _)return w;return v}function g(v){return v=m(v),d[v]||(d[v]=b(v))}function y(v,_,T){_=g(_),v.style[_]=T}return function(v,_){var T=arguments,A,w;if(T.length==2)for(A in _)w=_[A],w!==void 0&&_.hasOwnProperty(A)&&y(v,A,w);else y(v,T[1],T[2])}}();function a(c,d){var m=typeof c=="string"?c:f(c);return m.indexOf(" "+d+" ")>=0}function l(c,d){var m=f(c),b=m+d;a(m,d)||(c.className=b.substring(1))}function u(c,d){var m=f(c),b;!a(c,d)||(b=m.replace(" "+d+" "," "),c.className=b.substring(1,b.length-1))}function f(c){return(" "+(c.className||"")+" ").replace(/\s+/gi," ")}function p(c){c&&c.parentNode&&c.parentNode.removeChild(c)}return e})});var wt=class{constructor(){this.arrays={}}add(t,r){this.arrays[t]||(this.arrays[t]=[]),this.arrays[t].push(r)}remove(t){this.arrays[t]&&delete this.arrays[t]}get(t){return this.arrays[t]||[]}each(t,r){return this.get(t).forEach(r)}},Fe=class{constructor(){this.arrays=new WeakMap}add(t,r){this.arrays.has(t)||this.arrays.set(t,[]),this.arrays.get(t).push(r)}remove(t){this.arrays.has(t)&&this.arrays.delete(t,[])}get(t){return this.arrays.has(t)?this.arrays.get(t):[]}each(t,r){return this.get(t).forEach(r)}};function yt(e,t,r={},n=!0){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:n,composed:!0,cancelable:!0}))}function xt(e,t,r){return e.addEventListener(t,r),()=>e.removeEventListener(t,r)}function _t(e){return typeof e=="object"&&e!==null}function ri(e){return _t(e)&&!vr(e)}function vr(e){return Array.isArray(e)}function br(e){return typeof e=="function"}function ni(e){return typeof e!="object"||e===null}function ce(e){return JSON.parse(JSON.stringify(e))}function W(e,t){return t===""?e:t.split(".").reduce((r,n)=>{if(r!==void 0)return r[n]},e)}function ye(e,t,r){let n=t.split(".");if(n.length===1)return e[t]=r;let i=n.shift(),o=n.join(".");e[i]===void 0&&(e[i]={}),ye(e[i],o,r)}function Qe(e,t,r={},n=""){if(e===t)return r;if(typeof e!=typeof t||ri(e)&&vr(t)||vr(e)&&ri(t)||ni(e)||ni(t))return r[n]=t,r;let i=Object.keys(e);return Object.entries(t).forEach(([o,s])=>{r={...r,...Qe(e[o],t[o],r,n===""?o:`${n}.${o}`)},i=i.filter(a=>a!==o)}),i.forEach(o=>{r[`${n}.${o}`]="__rm__"}),r}function xe(e){let t=ii(e)?e[0]:e,r=ii(e)?e[1]:void 0;return _t(t)&&Object.entries(t).forEach(([n,i])=>{t[n]=xe(i)}),t}function ii(e){return Array.isArray(e)&&e.length===2&&typeof e[1]=="object"&&Object.keys(e[1]).includes("s")}function St(){if(document.querySelector('meta[name="csrf-token"]'))return document.querySelector('meta[name="csrf-token"]').getAttribute("content");if(document.querySelector("[data-csrf]"))return document.querySelector("[data-csrf]").getAttribute("data-csrf");if(window.livewireScriptConfig.csrf??!1)return window.livewireScriptConfig.csrf;throw"Livewire: No CSRF token detected"}var Ie;function oi(){if(Ie)return Ie;if(window.livewireScriptConfig&&(window.livewireScriptConfig.nonce??!1))return Ie=window.livewireScriptConfig.nonce,Ie;let e=document.querySelector("style[data-livewire-style][nonce]");return e?(Ie=e.nonce,Ie):null}function si(){return document.querySelector("[data-update-uri]")?.getAttribute("data-update-uri")??window.livewireScriptConfig.uri??null}function Et(e){return!!e.match(/ diff --git a/resources/views/settings/security.blade.php b/resources/views/settings/security.blade.php index 1b04f7d58a..a51c86fccb 100644 --- a/resources/views/settings/security.blade.php +++ b/resources/views/settings/security.blade.php @@ -16,9 +16,10 @@ - {{ Form::open(['method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form' ]) }} +
+ - {{csrf_field()}} + {{ csrf_field() }}
diff --git a/resources/views/setup/user.blade.php b/resources/views/setup/user.blade.php index 20bc861b85..b35060b01f 100644 --- a/resources/views/setup/user.blade.php +++ b/resources/views/setup/user.blade.php @@ -21,7 +21,7 @@ - {{ Form::text('site_name', old('site_name'), array('class' => 'form-control','placeholder' => 'Snipe-IT Asset Management')) }} + {{ Form::text('site_name', old('site_name'), array('class' => 'form-control','placeholder' => 'Snipe-IT Asset Management', 'required' => true)) }} {!! $errors->first('site_name', '') !!}
@@ -30,7 +30,7 @@
-
+
@@ -39,7 +39,7 @@
-
+
{{ Form::label('default_currency', trans('admin/settings/general.default_currency')) }} {{ Form::text('default_currency', old('default_currency'), array('class' => 'form-control','placeholder' => 'USD', 'maxlength'=>'3', 'style'=>'width: 60px;')) }} @@ -70,14 +70,14 @@
-
+
{{ Form::label('auto_increment_prefix', trans('admin/settings/general.auto_increment_prefix')) }} {{ Form::text('auto_increment_prefix', old('auto_increment_prefix'), array('class' => 'form-control')) }} {!! $errors->first('auto_increment_prefix', '') !!}
-
+
{{ Form::label('zerofill_count', trans('admin/settings/general.zerofill_count')) }} {{ Form::text('zerofill_count', old('zerofill_count', 5), array('class' => 'form-control')) }} @@ -90,14 +90,14 @@
{{ Form::label('email_domain', trans('general.email_domain')) }} - {{ Form::text('email_domain', old('email_domain'), array('class' => 'form-control','placeholder' => 'example.com')) }} + {{ Form::text('email_domain', old('email_domain'), array('class' => 'form-control','placeholder' => 'example.com','required' => true)) }} {{ trans('general.email_domain_help') }} {!! $errors->first('email_domain', '') !!}
-
+
{{ Form::label('email_format', trans('general.email_format')) }} {!! Form::username_format('email_format', old('email_format', 'filastname'), 'select2') !!} {!! $errors->first('email_format', '') !!} @@ -107,32 +107,32 @@
-
- {{ Form::label('first_name', trans('general.first_name')) }} - {{ Form::text('first_name', old('first_name'), array('class' => 'form-control','placeholder' => 'Jane')) }} +
+ {{ Form::label('first_name', trans('general.first_name'), 'required') }} + {{ Form::text('first_name', old('first_name'), array('class' => 'form-control','placeholder' => 'Jane', 'required' => true)) }} {!! $errors->first('first_name', '') !!}
{{ Form::label('last_name', trans('general.last_name')) }} - {{ Form::text('last_name', old('last_name'), array('class' => 'form-control','placeholder' => 'Smith')) }} + {{ Form::text('last_name', old('last_name'), array('class' => 'form-control','placeholder' => 'Smith', 'required' => true)) }} {!! $errors->first('last_name', '') !!}
-
+
{{ Form::label('email', trans('admin/users/table.email')) }} - {{ Form::email('email', config('mail.from.address'), array('class' => 'form-control','placeholder' => 'you@example.com')) }} + {{ Form::email('email', config('mail.from.address'), array('class' => 'form-control','placeholder' => 'you@example.com', 'required' => true)) }} {!! $errors->first('email', '') !!}
{{ Form::label('username', trans('admin/users/table.username')) }} - {{ Form::text('username', old('username'), array('class' => 'form-control','placeholder' => 'jsmith')) }} + {{ Form::text('username', old('username'), array('class' => 'form-control','placeholder' => 'jsmith', 'required' => true)) }} {!! $errors->first('username', '') !!}
@@ -141,14 +141,14 @@
{{ Form::label('password', trans('admin/users/table.password')) }} - {{ Form::password('password', array('class' => 'form-control')) }} + {{ Form::password('password', array('class' => 'form-control','required' => true)) }} {!! $errors->first('password', '') !!}
{{ Form::label('password_confirmation', trans('admin/users/table.password_confirm')) }} - {{ Form::password('password_confirmation', array('class' => 'form-control')) }} + {{ Form::password('password_confirmation', array('class' => 'form-control','required' => true)) }} {!! $errors->first('password_confirmation', '') !!}
diff --git a/resources/views/statuslabels/index.blade.php b/resources/views/statuslabels/index.blade.php index 7fdf4fc9a5..b5ded48234 100755 --- a/resources/views/statuslabels/index.blade.php +++ b/resources/views/statuslabels/index.blade.php @@ -25,6 +25,7 @@
- - - - - - - - - - - - -
{{ trans('general.id') }}{{ trans('admin/statuslabels/table.name') }}{{ trans('admin/statuslabels/table.status_type') }}{{ trans('general.assets') }}{{ trans('admin/statuslabels/table.color') }}{{ trans('admin/statuslabels/table.show_in_nav') }}{{ trans('admin/statuslabels/table.default_label') }}{{ trans('general.notes') }}{{ trans('table.actions') }}
diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index fc342897a7..223f9992cf 100755 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -86,28 +86,16 @@
-
- -
- - {!! $errors->first('first_name', '') !!} -
-
+ @include('partials.forms.edit.name-first') -
- -
- - {!! $errors->first('last_name', '') !!} -
-
+ @include('partials.forms.edit.name-last')
-
+
@if ($user->ldap_import!='1' || str_contains(Route::currentRouteName(), 'clone')) id)) ? ' disabled' : '') }} > @@ -150,7 +139,7 @@ -
+
@if ($user->ldap_import!='1' || str_contains(Route::currentRouteName(), 'clone') ) id)) ? ' required' : '' }} onfocus="this.removeAttribute('readonly');" {{ ((config('app.lock_passwords') && ($user->id)) ? ' disabled' : '') }}> @else @@ -183,7 +173,7 @@ -
+
id) ? ' required' : '' }} onfocus="this.removeAttribute('readonly');" {{ ((config('app.lock_passwords') && ($user->id)) ? ' disabled' : '') }} > @@ -237,11 +228,10 @@
-
-
+
id)) ? ' disabled' : '') }} autocomplete="off" readonly + {{ (Helper::checkIfRequired($user, 'email')) ? ' required' : '' }} onfocus="this.removeAttribute('readonly');"> @if (config('app.lock_passwords') && ($user->id))

{{ trans('admin/users/table.lock_passwords') }}

diff --git a/resources/views/users/print.blade.php b/resources/views/users/print.blade.php index 26ed2e8127..40575e7efc 100644 --- a/resources/views/users/print.blade.php +++ b/resources/views/users/print.blade.php @@ -2,7 +2,11 @@ - {{ trans('general.assigned_to', ['name' => $show_user->present()->fullName()]) }} - {{ date('Y-m-d H:i', time()) }} + @if ((isset($users) && count($users) === 1)) + {{ trans('general.assigned_to', ['name' => $users[0]->present()->fullName()]) }} - {{ date('Y-m-d H:i', time()) }} + @else + {{ trans('admin/users/general.print_assigned') }} - {{ date('Y-m-d H:i', time()) }} + @endisset @@ -32,7 +36,7 @@ @page { size: A4; } - + .print-logo { max-height: 40px; } @@ -43,17 +47,20 @@ } - +{{-- If we are rendering multiple users we'll add the ability to show/hide EULAs for all of them at once via this button --}} +@if (count($users) > 1) +
+ {{ trans('general.show_or_hide_eulas') }} + +
+@endif + @if ($snipeSettings->logo_print_assets=='1') @if ($snipeSettings->brand == '3') @@ -72,39 +79,39 @@ @endif @endif -

- {{ trans('general.assigned_to', ['name' => $show_user->present()->fullName()]) }} - {{ ($show_user->employee_num!='') ? ' (#'.$show_user->employee_num.') ' : '' }} - {{ ($show_user->jobtitle!='' ? ' - '.$show_user->jobtitle : '') }} -

-

{{ trans('admin/users/general.all_assigned_list_generation')}} {{ Helper::getFormattedDateObject(now(), 'datetime', false) }} +@foreach ($users as $show_user) +
{{-- used for page breaks when printing --}}
+

+ {{ trans('general.assigned_to', ['name' => $show_user->present()->fullName()]) }} + {{ ($show_user->employee_num!='') ? ' (#'.$show_user->employee_num.') ' : '' }} + {{ ($show_user->jobtitle!='' ? ' - '.$show_user->jobtitle : '') }} +

+

{{ trans('admin/users/general.all_assigned_list_generation')}} {{ Helper::getFormattedDateObject(now(), 'datetime', false) }} - - - @if ($assets->count() > 0) + @if ($show_user->assets->count() > 0) @php $counter = 1; @endphp
-

{{ trans_choice('general.countable.assets', $assets->count(), ['count' => $assets->count()]) }} +

{{ trans_choice('general.countable.assets', $show_user->assets->count(), ['count' => $show_user->assets->count()]) }}

+ class="snipe-table table table-striped inventory" + id="AssetsAssigned" + data-pagination="false" + data-id-table="AssetsAssigned" + data-search="false" + data-side-pagination="client" + data-sortable="true" + data-toolbar="#assets-toolbar" + data-show-columns="true" + data-sort-order="desc" + data-sort-name="created_at" + data-show-columns-toggle-all="true" + data-cookie-id-table="AssetsAssigned"> @@ -119,9 +126,9 @@ - @foreach ($assets as $asset) + @foreach ($show_user->assets as $asset) @php - if ($asset->model->category->getEula()) $eulas[] = $asset->model->category->getEula() + if (($asset->model->category) && ($asset->model->category->getEula())) $eulas[] = $asset->model->category->getEula() @endphp @@ -185,25 +192,25 @@
# {{ trans('general.image') }}{{ trans('general.signature') }}
{{ $counter }}
@endif - @if ($licenses->count() > 0) + @if ($show_user->licenses->count() > 0)
-

{{ trans_choice('general.countable.licenses', $licenses->count(), ['count' => $licenses->count()]) }}

+

{{ trans_choice('general.countable.licenses', $show_user->licenses->count(), ['count' => $show_user->licenses->count()]) }}

+ class="snipe-table table table-striped inventory" + id="licensessAssigned" + data-toolbar="#licenses-toolbar" + data-pagination="false" + data-id-table="licensessAssigned" + data-search="false" + data-side-pagination="client" + data-sortable="true" + data-show-columns="true" + data-sort-order="desc" + data-sort-name="created_at" + data-show-columns-toggle-all="true" + data-cookie-id-table="licensessAssigned"> @@ -216,9 +223,9 @@ $lcounter = 1; @endphp - @foreach ($licenses as $license) + @foreach ($show_user->licenses as $license) @php - if ($license->category->getEula()) $eulas[] = $license->category->getEula() + if (($license->category) && ($license->category->getEula())) $eulas[] = $license->category->getEula() @endphp @@ -240,25 +247,25 @@ @endif - @if ($accessories->count() > 0) + @if ($show_user->accessories->count() > 0)
-

{{ trans_choice('general.countable.accessories', $accessories->count(), ['count' => $accessories->count()]) }}

+

{{ trans_choice('general.countable.accessories', $show_user->accessories->count(), ['count' => $show_user->accessories->count()]) }}

#
{{ $lcounter }}
+ class="snipe-table table table-striped inventory" + id="accessoriesAssigned" + data-toolbar="#accessories-toolbar" + data-pagination="false" + data-id-table="accessoriesAssigned" + data-search="false" + data-side-pagination="client" + data-sortable="true" + data-show-columns="true" + data-sort-order="desc" + data-sort-name="created_at" + data-show-columns-toggle-all="true" + data-cookie-id-table="accessoriesAssigned"> @@ -273,10 +280,10 @@ $acounter = 1; @endphp - @foreach ($accessories as $accessory) + @foreach ($show_user->accessories as $accessory) @if ($accessory) @php - if ($accessory->category->getEula()) $eulas[] = $accessory->category->getEula() + if (($accessory->category) && ($accessory->category->getEula())) $eulas[] = $accessory->category->getEula() @endphp @@ -291,7 +298,7 @@ @@ -303,25 +310,25 @@
#
{{ $acounter }} @if (($accessory->assetlog->first()) && ($accessory->assetlog->first()->accept_signature!='')) - + @endif
@endif - @if ($consumables->count() > 0) + @if ($show_user->consumables->count() > 0)
-

{{ trans_choice('general.countable.consumables', $consumables->count(), ['count' => $consumables->count()]) }}

+

{{ trans_choice('general.countable.consumables', $show_user->consumables->count(), ['count' => $show_user->consumables->count()]) }}

+ class="snipe-table table table-striped inventory" + id="consumablesAssigned" + data-pagination="false" + data-toolbar="#consumables-toolbar" + data-id-table="consumablesAssigned" + data-search="false" + data-side-pagination="client" + data-sortable="true" + data-show-columns="true" + data-sort-order="desc" + data-sort-name="created_at" + data-show-columns-toggle-all="true" + data-cookie-id-table="consumablesAssigned"> @@ -336,10 +343,10 @@ $ccounter = 1; @endphp - @foreach ($consumables as $consumable) + @foreach ($show_user->consumables as $consumable) @if ($consumable) @php - if ($consumable->category->getEula()) $eulas[] = $consumable->category->getEula() + if (($consumable->category) && ($consumable->category->getEula())) $eulas[] = $consumable->category->getEula() @endphp @@ -347,7 +354,7 @@ @if ($consumable->deleted_at!='') @else - {{ ($consumable->manufacturer) ? $consumable->manufacturer->name : '' }} {{ $consumable->name }} {{ $consumable->model_number }} + {{ ($consumable->manufacturer) ? $consumable->manufacturer->name : '' }} {{ $consumable->name }} {{ $consumable->model_number }} @endif @@ -366,27 +373,30 @@
{{ $ccounter }}{{ ($consumable->manufacturer) ? $consumable->manufacturer->name : '' }} {{ $consumable->name }} {{ $consumable->model_number }} {{ ($consumable->category) ? $consumable->category->name : ' invalid/deleted category' }}
@endif -

-
- -
+ @php + if (!empty($eulas)) $eulas = array_unique($eulas); + @endphp + {{-- This may have been render at the top of the page if we're rendering more than one user... --}} + @if (count($users) === 1 && !empty($eulas)) +

+
+ +
+ @endif - - +
+ @if (!empty($eulas)) + - + + @endif @@ -414,6 +424,7 @@
EULA - @php - if (!empty($eulas)) $eulas = array_unique($eulas); - @endphp - @if (!empty($eulas)) - @foreach ($eulas as $key => $eula) - {!! $eula !!} - @endforeach - @endif + @foreach ($eulas as $key => $eula) + {!! $eula !!} + @endforeach
{{ trans('general.signed_off_by') }}: ______________________________________
+@endforeach {{-- Javascript files --}} diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 76a53b37fd..4ef3a0c24f 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -31,7 +31,7 @@ @@ -177,8 +177,6 @@
{{ $user->present()->fullName() }}
- - @can('update', $user)
@@ -673,7 +671,7 @@ {{ trans('admin/users/general.two_factor_active') }}
- @if ($user->two_factor_active()) == '1') + @if ($user->two_factor_active()) {{ trans('general.yes') }} @else @@ -690,7 +688,7 @@ {{ trans('admin/users/general.two_factor_enrolled') }}
- @if ($user->two_factor_active_and_enrolled()) == '1') + @if ($user->two_factor_active_and_enrolled()) {{ trans('general.yes') }} @else diff --git a/routes/api.php b/routes/api.php index 108f2ac231..0581a04682 100644 --- a/routes/api.php +++ b/routes/api.php @@ -495,24 +495,17 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi )->name('api.assets.show.byserial') ->where('any', '.*'); - // LEGACY URL - Get assets that are due or overdue for audit - Route::get('audit/{status}', - [ - Api\AssetsController::class, - 'index' - ] - )->name('api.asset.to-audit'); - // This gets the "due or overdue" API endpoints for audits and checkins + // This gets the "due or overdue" API endpoints for audit/audits and checkins Route::get('{action}/{upcoming_status}', [ Api\AssetsController::class, 'index' ] )->name('api.assets.list-upcoming') - ->where(['action' => 'audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']); + ->where(['action' => 'audit|audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']); diff --git a/routes/web.php b/routes/web.php index 6ccb991afe..1391976857 100644 --- a/routes/web.php +++ b/routes/web.php @@ -543,13 +543,16 @@ Route::group(['middleware' => 'web'], function () { )->name('logout.post'); }); -//Auth::routes(); -Route::get( - '/health', +/** + * Health check route - skip middleware + */ +Route::withoutMiddleware(['web'])->get( + '/health', [HealthController::class, 'get'] )->name('health'); + Route::middleware(['auth'])->get( '/', [DashboardController::class, 'index'] diff --git a/routes/web/users.php b/routes/web/users.php index e55541a937..e6aaf644ae 100644 --- a/routes/web/users.php +++ b/routes/web/users.php @@ -153,4 +153,4 @@ Route::resource('users', Users\UsersController::class, [ 'middleware' => ['auth'], 'parameters' => ['user' => 'user_id'], 'except' => ['update'] -]); \ No newline at end of file +]); diff --git a/tests/Concerns/TestsFullMultipleCompaniesSupport.php b/tests/Concerns/TestsFullMultipleCompaniesSupport.php new file mode 100644 index 0000000000..a52d27bd22 --- /dev/null +++ b/tests/Concerns/TestsFullMultipleCompaniesSupport.php @@ -0,0 +1,8 @@ +count(2)->create(); + + $accessoryA = Accessory::factory()->for($companyA)->create(); + $accessoryB = Accessory::factory()->for($companyB)->create(); + + $superuser = User::factory()->superuser()->create(); + $userInCompanyA = $companyA->users()->save(User::factory()->viewAccessories()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewAccessories()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.accessories.selectlist')) + ->assertOk() + ->assertJsonPath('total_count', 1) + ->assertResponseContainsInResults($accessoryA) + ->assertResponseDoesNotContainInResults($accessoryB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.accessories.selectlist')) + ->assertOk() + ->assertJsonPath('total_count', 1) + ->assertResponseDoesNotContainInResults($accessoryA) + ->assertResponseContainsInResults($accessoryB); + + $this->actingAsForApi($superuser) + ->getJson(route('api.accessories.selectlist')) + ->assertOk() + ->assertJsonPath('total_count', 2) + ->assertResponseContainsInResults($accessoryA) + ->assertResponseContainsInResults($accessoryB); + } + + public function testCanGetAccessoriesForSelectList() + { + [$accessoryA, $accessoryB] = Accessory::factory()->count(2)->create(); + + $this->actingAsForApi(User::factory()->viewAccessories()->create()) + ->getJson(route('api.accessories.selectlist')) + ->assertOk() + ->assertJsonPath('total_count', 2) + ->assertResponseContainsInResults($accessoryA) + ->assertResponseContainsInResults($accessoryB); + } +} diff --git a/tests/Feature/Accessories/Api/DeleteAccessoriesTest.php b/tests/Feature/Accessories/Api/DeleteAccessoriesTest.php new file mode 100644 index 0000000000..f6a15118f5 --- /dev/null +++ b/tests/Feature/Accessories/Api/DeleteAccessoriesTest.php @@ -0,0 +1,77 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.accessories.destroy', $accessory)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($accessory); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $accessoryA = Accessory::factory()->for($companyA)->create(); + $accessoryB = Accessory::factory()->for($companyB)->create(); + $accessoryC = Accessory::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteAccessories()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteAccessories()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.accessories.destroy', $accessoryB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.accessories.destroy', $accessoryA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.accessories.destroy', $accessoryC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($accessoryA); + $this->assertNotSoftDeleted($accessoryB); + $this->assertSoftDeleted($accessoryC); + } + + public function testCannotDeleteAccessoryThatHasCheckouts() + { + $accessory = Accessory::factory()->checkedOutToUser()->create(); + + $this->actingAsForApi(User::factory()->deleteAccessories()->create()) + ->deleteJson(route('api.accessories.destroy', $accessory)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($accessory); + } + + public function testCanDeleteAccessory() + { + $accessory = Accessory::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteAccessories()->create()) + ->deleteJson(route('api.accessories.destroy', $accessory)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($accessory); + } +} diff --git a/tests/Feature/Accessories/Api/DeleteAccessoryTest.php b/tests/Feature/Accessories/Api/DeleteAccessoryTest.php deleted file mode 100644 index d1d732e754..0000000000 --- a/tests/Feature/Accessories/Api/DeleteAccessoryTest.php +++ /dev/null @@ -1,19 +0,0 @@ -create(); - - $this->actingAsForApi(User::factory()->create()) - ->deleteJson(route('api.accessories.destroy', $accessory)) - ->assertForbidden(); - } -} diff --git a/tests/Feature/Accessories/Api/IndexAccessoryCheckoutsTest.php b/tests/Feature/Accessories/Api/IndexAccessoryCheckoutsTest.php new file mode 100644 index 0000000000..38f1e99e5d --- /dev/null +++ b/tests/Feature/Accessories/Api/IndexAccessoryCheckoutsTest.php @@ -0,0 +1,84 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->getJson(route('api.accessories.checkedout', $accessory)) + ->assertForbidden(); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $accessoryA = Accessory::factory()->for($companyA)->create(); + $accessoryB = Accessory::factory()->for($companyB)->create(); + + $superuser = User::factory()->superuser()->create(); + $userInCompanyA = $companyA->users()->save(User::factory()->viewAccessories()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewAccessories()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.accessories.checkedout', $accessoryB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.accessories.checkedout', $accessoryA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superuser) + ->getJson(route('api.accessories.checkedout', $accessoryA)) + ->assertOk(); + } + + public function testCanGetAccessoryCheckouts() + { + [$userA, $userB] = User::factory()->count(2)->create(); + + $accessory = Accessory::factory()->checkedOutToUsers([$userA, $userB])->create(); + + $this->assertEquals(2, $accessory->checkouts()->count()); + + $this->actingAsForApi(User::factory()->viewAccessories()->create()) + ->getJson(route('api.accessories.checkedout', $accessory)) + ->assertOk() + ->assertJsonPath('total', 2) + ->assertJsonPath('rows.0.assigned_to.id', $userA->id) + ->assertJsonPath('rows.1.assigned_to.id', $userB->id); + } + + public function testCanGetAccessoryCheckoutsWithOffsetAndLimitInQueryString() + { + [$userA, $userB, $userC] = User::factory()->count(3)->create(); + + $accessory = Accessory::factory()->checkedOutToUsers([$userA, $userB, $userC])->create(); + + $actor = $this->actingAsForApi(User::factory()->viewAccessories()->create()); + + $actor->getJson(route('api.accessories.checkedout', ['accessory' => $accessory->id, 'limit' => 1])) + ->assertOk() + ->assertJsonPath('total', 3) + ->assertJsonPath('rows.0.assigned_to.id', $userA->id); + + $actor->getJson(route('api.accessories.checkedout', ['accessory' => $accessory->id, 'limit' => 2, 'offset' => 1])) + ->assertOk() + ->assertJsonPath('total', 3) + ->assertJsonPath('rows.0.assigned_to.id', $userB->id) + ->assertJsonPath('rows.1.assigned_to.id', $userC->id); + } +} diff --git a/tests/Feature/Accessories/Api/IndexAccessoryTest.php b/tests/Feature/Accessories/Api/IndexAccessoryTest.php index 509e6fd452..96263f5380 100644 --- a/tests/Feature/Accessories/Api/IndexAccessoryTest.php +++ b/tests/Feature/Accessories/Api/IndexAccessoryTest.php @@ -2,15 +2,69 @@ namespace Tests\Feature\Accessories\Api; +use App\Models\Accessory; +use App\Models\Company; use App\Models\User; +use Tests\Concerns\TestsFullMultipleCompaniesSupport; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class IndexAccessoryTest extends TestCase +class IndexAccessoryTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement { - public function testPermissionRequiredToViewAccessoriesIndex() + public function testRequiresPermission() { $this->actingAsForApi(User::factory()->create()) ->getJson(route('api.accessories.index')) ->assertForbidden(); } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $accessoryA = Accessory::factory()->for($companyA)->create(['name' => 'Accessory A']); + $accessoryB = Accessory::factory()->for($companyB)->create(['name' => 'Accessory B']); + $accessoryC = Accessory::factory()->for($companyB)->create(['name' => 'Accessory C']); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewAccessories()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewAccessories()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.accessories.index')) + ->assertOk() + ->assertResponseContainsInRows($accessoryA) + ->assertResponseDoesNotContainInRows($accessoryB) + ->assertResponseDoesNotContainInRows($accessoryC); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.accessories.index')) + ->assertOk() + ->assertResponseDoesNotContainInRows($accessoryA) + ->assertResponseContainsInRows($accessoryB) + ->assertResponseContainsInRows($accessoryC); + + $this->actingAsForApi($superUser) + ->getJson(route('api.accessories.index')) + ->assertOk() + ->assertResponseContainsInRows($accessoryA) + ->assertResponseContainsInRows($accessoryB) + ->assertResponseContainsInRows($accessoryC); + } + + public function testCanGetAccessories() + { + $user = User::factory()->viewAccessories()->create(); + + $accessoryA = Accessory::factory()->create(['name' => 'Accessory A']); + $accessoryB = Accessory::factory()->create(['name' => 'Accessory B']); + + $this->actingAsForApi($user) + ->getJson(route('api.accessories.index')) + ->assertOk() + ->assertResponseContainsInRows($accessoryA) + ->assertResponseContainsInRows($accessoryB); + } } diff --git a/tests/Feature/Accessories/Api/ShowAccessoryTest.php b/tests/Feature/Accessories/Api/ShowAccessoryTest.php index 2bc3e88bfc..117d725007 100644 --- a/tests/Feature/Accessories/Api/ShowAccessoryTest.php +++ b/tests/Feature/Accessories/Api/ShowAccessoryTest.php @@ -3,12 +3,15 @@ namespace Tests\Feature\Accessories\Api; use App\Models\Accessory; +use App\Models\Company; use App\Models\User; +use Tests\Concerns\TestsFullMultipleCompaniesSupport; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class ShowAccessoryTest extends TestCase +class ShowAccessoryTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement { - public function testPermissionRequiredToShowAccessory() + public function testRequiresPermission() { $accessory = Accessory::factory()->create(); @@ -16,4 +19,43 @@ class ShowAccessoryTest extends TestCase ->getJson(route('api.accessories.show', $accessory)) ->assertForbidden(); } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $accessoryForCompanyA = Accessory::factory()->for($companyA)->create(); + + $superuser = User::factory()->superuser()->create(); + $userForCompanyB = User::factory()->for($companyB)->viewAccessories()->create(); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userForCompanyB) + ->getJson(route('api.accessories.show', $accessoryForCompanyA)) + ->assertOk() + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superuser) + ->getJson(route('api.accessories.show', $accessoryForCompanyA)) + ->assertOk() + ->assertJsonFragment([ + 'id' => $accessoryForCompanyA->id, + ]); + } + + public function testCanGetSingleAccessory() + { + $accessory = Accessory::factory()->checkedOutToUser()->create(['name' => 'My Accessory']); + + $this->actingAsForApi(User::factory()->viewAccessories()->create()) + ->getJson(route('api.accessories.show', $accessory)) + ->assertOk() + ->assertJsonFragment([ + 'id' => $accessory->id, + 'name' => 'My Accessory', + 'checkouts_count' => 1, + ]); + + } } diff --git a/tests/Feature/Accessories/Api/StoreAccessoryTest.php b/tests/Feature/Accessories/Api/StoreAccessoryTest.php index d29ff32f6e..22ba715519 100644 --- a/tests/Feature/Accessories/Api/StoreAccessoryTest.php +++ b/tests/Feature/Accessories/Api/StoreAccessoryTest.php @@ -2,15 +2,97 @@ namespace Tests\Feature\Accessories\Api; +use App\Models\Category; +use App\Models\Company; +use App\Models\Location; +use App\Models\Manufacturer; +use App\Models\Supplier; use App\Models\User; +use Tests\Concerns\TestsFullMultipleCompaniesSupport; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class StoreAccessoryTest extends TestCase +class StoreAccessoryTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement { - public function testPermissionRequiredToStoreAccessory() + public function testRequiresPermission() { $this->actingAsForApi(User::factory()->create()) ->postJson(route('api.accessories.store')) ->assertForbidden(); } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + $this->markTestSkipped('This behavior is not implemented'); + + [$companyA, $companyB] = Company::factory()->count(2)->create(); + $userInCompanyA = User::factory()->for($companyA)->createAccessories()->create(); + + $this->settings->enableMultipleFullCompanySupport(); + + // attempt to store an accessory for company B + $this->actingAsForApi($userInCompanyA) + ->postJson(route('api.accessories.store'), [ + 'category_id' => Category::factory()->forAccessories()->create()->id, + 'name' => 'My Awesome Accessory', + 'qty' => 1, + 'company_id' => $companyB->id, + ])->assertStatusMessageIs('error'); + + $this->assertDatabaseMissing('accessories', [ + 'name' => 'My Awesome Accessory', + ]); + } + + public function testValidation() + { + $this->actingAsForApi(User::factory()->createAccessories()->create()) + ->postJson(route('api.accessories.store'), [ + // + ]) + ->assertStatusMessageIs('error') + ->assertMessagesContains([ + 'category_id', + 'name', + 'qty', + ]); + } + + public function testCanStoreAccessory() + { + $category = Category::factory()->forAccessories()->create(); + $company = Company::factory()->create(); + $location = Location::factory()->create(); + $manufacturer = Manufacturer::factory()->create(); + $supplier = Supplier::factory()->create(); + + $this->actingAsForApi(User::factory()->createAccessories()->create()) + ->postJson(route('api.accessories.store'), [ + 'name' => 'My Awesome Accessory', + 'qty' => 2, + 'order_number' => '12345', + 'purchase_cost' => 100.00, + 'purchase_date' => '2024-09-18', + 'model_number' => '98765', + 'category_id' => $category->id, + 'company_id' => $company->id, + 'location_id' => $location->id, + 'manufacturer_id' => $manufacturer->id, + 'supplier_id' => $supplier->id, + ])->assertStatusMessageIs('success'); + + $this->assertDatabaseHas('accessories', [ + 'name' => 'My Awesome Accessory', + 'qty' => 2, + 'order_number' => '12345', + 'purchase_cost' => 100.00, + 'purchase_date' => '2024-09-18', + 'model_number' => '98765', + 'category_id' => $category->id, + 'company_id' => $company->id, + 'location_id' => $location->id, + 'manufacturer_id' => $manufacturer->id, + 'supplier_id' => $supplier->id, + ]); + } } diff --git a/tests/Feature/Accessories/Api/UpdateAccessoryTest.php b/tests/Feature/Accessories/Api/UpdateAccessoryTest.php index 45923651f2..a0009a732a 100644 --- a/tests/Feature/Accessories/Api/UpdateAccessoryTest.php +++ b/tests/Feature/Accessories/Api/UpdateAccessoryTest.php @@ -3,12 +3,19 @@ namespace Tests\Feature\Accessories\Api; use App\Models\Accessory; +use App\Models\Category; +use App\Models\Company; +use App\Models\Location; +use App\Models\Manufacturer; +use App\Models\Supplier; use App\Models\User; +use Tests\Concerns\TestsFullMultipleCompaniesSupport; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class UpdateAccessoryTest extends TestCase +class UpdateAccessoryTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement { - public function testPermissionRequiredToUpdateAccessory() + public function testRequiresPermission() { $accessory = Accessory::factory()->create(); @@ -16,4 +23,84 @@ class UpdateAccessoryTest extends TestCase ->patchJson(route('api.accessories.update', $accessory)) ->assertForbidden(); } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $accessoryA = Accessory::factory()->for($companyA)->create(['name' => 'A Name to Change']); + $accessoryB = Accessory::factory()->for($companyB)->create(['name' => 'A Name to Change']); + $accessoryC = Accessory::factory()->for($companyB)->create(['name' => 'A Name to Change']); + + $superuser = User::factory()->superuser()->create(); + $userInCompanyA = $companyA->users()->save(User::factory()->editAccessories()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->editAccessories()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->patchJson(route('api.accessories.update', $accessoryB), ['name' => 'New Name']) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->patchJson(route('api.accessories.update', $accessoryA), ['name' => 'New Name']) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superuser) + ->patchJson(route('api.accessories.update', $accessoryC), ['name' => 'New Name']) + ->assertOk(); + + $this->assertEquals('A Name to Change', $accessoryA->fresh()->name); + $this->assertEquals('A Name to Change', $accessoryB->fresh()->name); + $this->assertEquals('New Name', $accessoryC->fresh()->name); + } + + public function testCanUpdateAccessoryViaPatch() + { + [$categoryA, $categoryB] = Category::factory()->count(2)->create(); + [$companyA, $companyB] = Company::factory()->count(2)->create(); + [$locationA, $locationB] = Location::factory()->count(2)->create(); + [$manufacturerA, $manufacturerB] = Manufacturer::factory()->count(2)->create(); + [$supplierA, $supplierB] = Supplier::factory()->count(2)->create(); + + $accessory = Accessory::factory()->create([ + 'name' => 'A Name to Change', + 'qty' => 5, + 'order_number' => 'A12345', + 'purchase_cost' => 99.99, + 'model_number' => 'ABC098', + 'category_id' => $categoryA->id, + 'company_id' => $companyA->id, + 'location_id' => $locationA->id, + 'manufacturer_id' => $manufacturerA->id, + 'supplier_id' => $supplierA->id, + ]); + + $this->actingAsForApi(User::factory()->editAccessories()->create()) + ->patchJson(route('api.accessories.update', $accessory), [ + 'name' => 'A New Name', + 'qty' => 10, + 'order_number' => 'B54321', + 'purchase_cost' => 199.99, + 'model_number' => 'XYZ123', + 'category_id' => $categoryB->id, + 'company_id' => $companyB->id, + 'location_id' => $locationB->id, + 'manufacturer_id' => $manufacturerB->id, + 'supplier_id' => $supplierB->id, + ]) + ->assertOk(); + + $accessory = $accessory->fresh(); + $this->assertEquals('A New Name', $accessory->name); + $this->assertEquals(10, $accessory->qty); + $this->assertEquals('B54321', $accessory->order_number); + $this->assertEquals(199.99, $accessory->purchase_cost); + $this->assertEquals('XYZ123', $accessory->model_number); + $this->assertEquals($categoryB->id, $accessory->category_id); + $this->assertEquals($companyB->id, $accessory->company_id); + $this->assertEquals($locationB->id, $accessory->location_id); + $this->assertEquals($manufacturerB->id, $accessory->manufacturer_id); + $this->assertEquals($supplierB->id, $accessory->supplier_id); + } } diff --git a/tests/Feature/Accessories/Ui/CreateAccessoriesTest.php b/tests/Feature/Accessories/Ui/CreateAccessoriesTest.php index 3570ef1704..f5cffd1be2 100644 --- a/tests/Feature/Accessories/Ui/CreateAccessoriesTest.php +++ b/tests/Feature/Accessories/Ui/CreateAccessoriesTest.php @@ -77,6 +77,6 @@ class CreateAccessoriesTest extends TestCase ->post(route('accessories.store'), array_merge($data, ['redirect_option' => 'index'])) ->assertRedirect(route('accessories.index')); - $this->assertDatabaseHas('accessories', array_merge($data, ['user_id' => $user->id])); + $this->assertDatabaseHas('accessories', array_merge($data, ['created_by' => $user->id])); } } diff --git a/tests/Feature/AssetMaintenances/Api/DeleteAssetMaintenancesTest.php b/tests/Feature/AssetMaintenances/Api/DeleteAssetMaintenancesTest.php new file mode 100644 index 0000000000..8a0189bc5b --- /dev/null +++ b/tests/Feature/AssetMaintenances/Api/DeleteAssetMaintenancesTest.php @@ -0,0 +1,70 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenance)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($assetMaintenance); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetMaintenanceA = AssetMaintenance::factory()->create(); + $assetMaintenanceB = AssetMaintenance::factory()->create(); + $assetMaintenanceC = AssetMaintenance::factory()->create(); + + $assetMaintenanceA->asset->update(['company_id' => $companyA->id]); + $assetMaintenanceB->asset->update(['company_id' => $companyB->id]); + $assetMaintenanceC->asset->update(['company_id' => $companyB->id]); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->editAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->editAssets()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenanceB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenanceA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenanceC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($assetMaintenanceA); + $this->assertNotSoftDeleted($assetMaintenanceB); + $this->assertSoftDeleted($assetMaintenanceC); + } + + public function testCanDeleteAssetMaintenance() + { + $assetMaintenance = AssetMaintenance::factory()->create(); + + $this->actingAsForApi(User::factory()->editAssets()->create()) + ->deleteJson(route('api.maintenances.destroy', $assetMaintenance)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($assetMaintenance); + } +} diff --git a/tests/Feature/AssetModels/Api/DeleteAssetModelsTest.php b/tests/Feature/AssetModels/Api/DeleteAssetModelsTest.php new file mode 100644 index 0000000000..a079788651 --- /dev/null +++ b/tests/Feature/AssetModels/Api/DeleteAssetModelsTest.php @@ -0,0 +1,45 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.models.destroy', $assetModel)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($assetModel); + } + + public function testCannotDeleteAssetModelThatStillHasAssociatedAssets() + { + $assetModel = Asset::factory()->create()->model; + + $this->actingAsForApi(User::factory()->deleteAssetModels()->create()) + ->deleteJson(route('api.models.destroy', $assetModel)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($assetModel); + } + + public function testCanDeleteAssetModel() + { + $assetModel = AssetModel::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteAssetModels()->create()) + ->deleteJson(route('api.models.destroy', $assetModel)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($assetModel); + } +} diff --git a/tests/Feature/Assets/Api/DeleteAssetsTest.php b/tests/Feature/Assets/Api/DeleteAssetsTest.php new file mode 100644 index 0000000000..5a017e0f5d --- /dev/null +++ b/tests/Feature/Assets/Api/DeleteAssetsTest.php @@ -0,0 +1,71 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.assets.destroy', $asset)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($asset); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetA = Asset::factory()->for($companyA)->create(); + $assetB = Asset::factory()->for($companyB)->create(); + $assetC = Asset::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteAssets()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.assets.destroy', $assetB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.assets.destroy', $assetA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.assets.destroy', $assetC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($assetA); + $this->assertNotSoftDeleted($assetB); + $this->assertSoftDeleted($assetC); + } + + public function testCannotDeleteAssetThatIsCheckedOut() + { + $this->markTestSkipped('This behavior is not functioning yet.'); + } + + public function testCanDeleteAsset() + { + $asset = Asset::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteAssets()->create()) + ->deleteJson(route('api.assets.destroy', $asset)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($asset); + } +} diff --git a/tests/Feature/Assets/Api/StoreAssetTest.php b/tests/Feature/Assets/Api/StoreAssetTest.php index 7ebfdf8702..a147504519 100644 --- a/tests/Feature/Assets/Api/StoreAssetTest.php +++ b/tests/Feature/Assets/Api/StoreAssetTest.php @@ -29,7 +29,7 @@ class StoreAssetTest extends TestCase $location = Location::factory()->create(); $model = AssetModel::factory()->create(); $rtdLocation = Location::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $supplier = Supplier::factory()->create(); $user = User::factory()->createAssets()->create(); $userAssigned = User::factory()->create(); @@ -61,7 +61,7 @@ class StoreAssetTest extends TestCase $asset = Asset::find($response['payload']['id']); - $this->assertTrue($asset->admin->is($user)); + $this->assertTrue($asset->adminuser->is($user)); $this->assertEquals('2024-06-02', $asset->asset_eol_date); $this->assertEquals('random_string', $asset->asset_tag); @@ -90,7 +90,7 @@ class StoreAssetTest extends TestCase 'last_audit_date' => '2023-09-03', 'asset_tag' => '1234', 'model_id' => AssetModel::factory()->create()->id, - 'status_id' => Statuslabel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id, ]) ->assertOk() ->assertStatusMessageIs('success'); @@ -106,7 +106,7 @@ class StoreAssetTest extends TestCase // 'last_audit_date' => '2023-09-03 12:23:45', 'asset_tag' => '1234', 'model_id' => AssetModel::factory()->create()->id, - 'status_id' => Statuslabel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id, ]) ->assertOk() ->assertStatusMessageIs('success'); @@ -122,17 +122,63 @@ class StoreAssetTest extends TestCase 'last_audit_date' => 'this-is-not-valid', 'asset_tag' => '1234', 'model_id' => AssetModel::factory()->create()->id, - 'status_id' => Statuslabel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id, ]) + ->assertOk() ->assertStatusMessageIs('error'); $this->assertNotNull($response->json('messages.last_audit_date')); } + public function testSaveWithArchivedStatusAndUserReturnsValidationError() + { + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'assigned_to' => '1', + 'assigned_type' => User::class, + 'model_id' => AssetModel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->archived()->create()->id, + ]) + ->assertOk() + ->assertStatusMessageIs('error'); + + $this->assertNotNull($response->json('messages.status_id')); + } + + public function testSaveWithPendingStatusAndUserReturnsValidationError() + { + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'assigned_to' => '1', + 'assigned_type' => User::class, + 'model_id' => AssetModel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->pending()->create()->id, + ]) + ->assertOk() + ->assertJson([ + 'messages' => ['status_id' => [trans('admin/hardware/form.asset_not_deployable')]] + ]); + + $this->assertNotNull($response->json('messages.status_id')); + } + + public function testSaveWithPendingStatusWithoutUserIsSuccessful() + { + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => '1234', + 'model_id' => AssetModel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->pending()->create()->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success'); + } + + public function testArchivedDepreciateAndPhysicalCanBeNull() { $model = AssetModel::factory()->ipadModel()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->enableAutoIncrement(); @@ -157,7 +203,7 @@ class StoreAssetTest extends TestCase public function testArchivedDepreciateAndPhysicalCanBeEmpty() { $model = AssetModel::factory()->ipadModel()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->enableAutoIncrement(); @@ -182,7 +228,7 @@ class StoreAssetTest extends TestCase public function testAssetEolDateIsCalculatedIfPurchaseDateSet() { $model = AssetModel::factory()->mbp13Model()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->enableAutoIncrement(); @@ -203,7 +249,7 @@ class StoreAssetTest extends TestCase public function testAssetEolDateIsNotCalculatedIfPurchaseDateNotSet() { $model = AssetModel::factory()->mbp13Model()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->enableAutoIncrement(); @@ -223,7 +269,7 @@ class StoreAssetTest extends TestCase public function testAssetEolExplicitIsSetIfAssetEolDateIsExplicitlySet() { $model = AssetModel::factory()->mbp13Model()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->enableAutoIncrement(); @@ -245,7 +291,7 @@ class StoreAssetTest extends TestCase public function testAssetGetsAssetTagWithAutoIncrement() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->enableAutoIncrement(); @@ -265,7 +311,7 @@ class StoreAssetTest extends TestCase public function testAssetCreationFailsWithNoAssetTagOrAutoIncrement() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $this->settings->disableAutoIncrement(); @@ -289,7 +335,7 @@ class StoreAssetTest extends TestCase ->postJson(route('api.assets.store'), [ 'asset_tag' => 'random-string', 'model_id' => AssetModel::factory()->create()->id, - 'status_id' => Statuslabel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id, // API accepts float 'purchase_cost' => 12.34, ]) @@ -311,7 +357,7 @@ class StoreAssetTest extends TestCase ->postJson(route('api.assets.store'), [ 'asset_tag' => 'random-string', 'model_id' => AssetModel::factory()->create()->id, - 'status_id' => Statuslabel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id, // API also accepts string for comma separated values 'purchase_cost' => '12,34', ]) @@ -325,7 +371,7 @@ class StoreAssetTest extends TestCase public function testUniqueSerialNumbersIsEnforcedWhenEnabled() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $serial = '1234567890'; $this->settings->enableAutoIncrement(); @@ -353,7 +399,7 @@ class StoreAssetTest extends TestCase public function testUniqueSerialNumbersIsNotEnforcedWhenDisabled() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $serial = '1234567890'; $this->settings->enableAutoIncrement(); @@ -381,7 +427,7 @@ class StoreAssetTest extends TestCase public function testAssetTagsMustBeUniqueWhenUndeleted() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $asset_tag = '1234567890'; $this->settings->disableAutoIncrement(); @@ -408,7 +454,7 @@ class StoreAssetTest extends TestCase public function testAssetTagsCanBeDuplicatedIfDeleted() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $asset_tag = '1234567890'; $this->settings->disableAutoIncrement(); @@ -438,7 +484,7 @@ class StoreAssetTest extends TestCase public function testAnAssetCanBeCheckedOutToUserOnStore() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $user = User::factory()->createAssets()->create(); $userAssigned = User::factory()->create(); @@ -456,7 +502,7 @@ class StoreAssetTest extends TestCase $asset = Asset::find($response['payload']['id']); - $this->assertTrue($asset->admin->is($user)); + $this->assertTrue($asset->adminuser->is($user)); $this->assertTrue($asset->checkedOutToUser()); $this->assertTrue($asset->assignedTo->is($userAssigned)); } @@ -464,7 +510,7 @@ class StoreAssetTest extends TestCase public function testAnAssetCanBeCheckedOutToLocationOnStore() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $location = Location::factory()->create(); $user = User::factory()->createAssets()->create(); @@ -482,7 +528,7 @@ class StoreAssetTest extends TestCase $asset = Asset::find($response['payload']['id']); - $this->assertTrue($asset->admin->is($user)); + $this->assertTrue($asset->adminuser->is($user)); $this->assertTrue($asset->checkedOutToLocation()); $this->assertTrue($asset->location->is($location)); } @@ -490,7 +536,7 @@ class StoreAssetTest extends TestCase public function testAnAssetCanBeCheckedOutToAssetOnStore() { $model = AssetModel::factory()->create(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $asset = Asset::factory()->create(); $user = User::factory()->createAssets()->create(); @@ -508,7 +554,7 @@ class StoreAssetTest extends TestCase $apiAsset = Asset::find($response['payload']['id']); - $this->assertTrue($apiAsset->admin->is($user)); + $this->assertTrue($apiAsset->adminuser->is($user)); $this->assertTrue($apiAsset->checkedOutToAsset()); // I think this makes sense, but open to a sanity check $this->assertTrue($asset->assignedAssets()->find($response['payload']['id'])->is($apiAsset)); @@ -530,7 +576,7 @@ class StoreAssetTest extends TestCase { $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $field = CustomField::factory()->testEncrypted()->create(); $superuser = User::factory()->superuser()->create(); $assetData = Asset::factory()->hasEncryptedCustomField($field)->make(); @@ -555,7 +601,7 @@ class StoreAssetTest extends TestCase // @todo: $this->markTestIncomplete(); - $status = Statuslabel::factory()->create(); + $status = Statuslabel::factory()->readyToDeploy()->create(); $field = CustomField::factory()->testEncrypted()->create(); $normal_user = User::factory()->editAssets()->create(); $assetData = Asset::factory()->hasEncryptedCustomField($field)->make(); diff --git a/tests/Feature/Assets/Api/UpdateAssetTest.php b/tests/Feature/Assets/Api/UpdateAssetTest.php index 7a4e411b3d..df4448a2db 100644 --- a/tests/Feature/Assets/Api/UpdateAssetTest.php +++ b/tests/Feature/Assets/Api/UpdateAssetTest.php @@ -534,7 +534,7 @@ class UpdateAssetTest extends TestCase 'company_id' => $companyB->id, ]); $asset = Asset::factory()->create([ - 'user_id' => $userA->id, + 'created_by' => $userA->id, 'company_id' => $companyA->id, ]); diff --git a/tests/Feature/Categories/Api/DeleteCategoriesTest.php b/tests/Feature/Categories/Api/DeleteCategoriesTest.php new file mode 100644 index 0000000000..eb9b73b050 --- /dev/null +++ b/tests/Feature/Categories/Api/DeleteCategoriesTest.php @@ -0,0 +1,46 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.categories.destroy', $category)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($category); + } + + public function testCannotDeleteCategoryThatStillHasAssociatedItems() + { + $asset = Asset::factory()->create(); + $category = $asset->model->category; + + $this->actingAsForApi(User::factory()->deleteCategories()->create()) + ->deleteJson(route('api.categories.destroy', $category)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($category); + } + + public function testCanDeleteCategory() + { + $category = Category::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCategories()->create()) + ->deleteJson(route('api.categories.destroy', $category)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($category); + } +} diff --git a/tests/Feature/Checkins/Api/AccessoryCheckinTest.php b/tests/Feature/Checkins/Api/AccessoryCheckinTest.php new file mode 100644 index 0000000000..d5f45f22ae --- /dev/null +++ b/tests/Feature/Checkins/Api/AccessoryCheckinTest.php @@ -0,0 +1,86 @@ +checkedOutToUser()->create(); + $accessoryCheckout = $accessory->checkouts->first(); + + $this->actingAsForApi(User::factory()->create()) + ->postJson(route('api.accessories.checkin', $accessoryCheckout)) + ->assertForbidden(); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = User::factory()->for($companyA)->checkinAccessories()->create(); + $accessoryForCompanyB = Accessory::factory()->for($companyB)->checkedOutToUser()->create(); + $anotherAccessoryForCompanyB = Accessory::factory()->for($companyB)->checkedOutToUser()->create(); + + $this->assertEquals(1, $accessoryForCompanyB->checkouts->count()); + $this->assertEquals(1, $anotherAccessoryForCompanyB->checkouts->count()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->postJson(route('api.accessories.checkin', $accessoryForCompanyB->checkouts->first())) + ->assertForbidden(); + + $this->actingAsForApi($superUser) + ->postJson(route('api.accessories.checkin', $anotherAccessoryForCompanyB->checkouts->first())) + ->assertStatusMessageIs('success'); + + $this->assertEquals(1, $accessoryForCompanyB->fresh()->checkouts->count(), 'Accessory should not be checked in'); + $this->assertEquals(0, $anotherAccessoryForCompanyB->fresh()->checkouts->count(), 'Accessory should be checked in'); + } + + public function testCanCheckinAccessory() + { + $accessory = Accessory::factory()->checkedOutToUser()->create(); + + $this->assertEquals(1, $accessory->checkouts->count()); + + $accessoryCheckout = $accessory->checkouts->first(); + + $this->actingAsForApi(User::factory()->checkinAccessories()->create()) + ->postJson(route('api.accessories.checkin', $accessoryCheckout)) + ->assertStatusMessageIs('success'); + + $this->assertEquals(0, $accessory->fresh()->checkouts->count(), 'Accessory should be checked in'); + } + + public function testCheckinIsLogged() + { + $user = User::factory()->create(); + $actor = User::factory()->checkinAccessories()->create(); + + $accessory = Accessory::factory()->checkedOutToUser($user)->create(); + $accessoryCheckout = $accessory->checkouts->first(); + + $this->actingAsForApi($actor) + ->postJson(route('api.accessories.checkin', $accessoryCheckout)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseHas('action_logs', [ + 'created_by' => $actor->id, + 'action_type' => 'checkin from', + 'target_id' => $user->id, + 'target_type' => User::class, + 'item_id' => $accessory->id, + 'item_type' => Accessory::class, + ]); + } +} diff --git a/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php b/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php index 2b37797fb6..765b8436a0 100644 --- a/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php +++ b/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php @@ -7,11 +7,12 @@ use App\Models\Actionlog; use App\Models\User; use App\Notifications\CheckoutAccessoryNotification; use Illuminate\Support\Facades\Notification; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class AccessoryCheckoutTest extends TestCase +class AccessoryCheckoutTest extends TestCase implements TestsPermissionsRequirement { - public function testCheckingOutAccessoryRequiresCorrectPermission() + public function testRequiresPermission() { $this->actingAsForApi(User::factory()->create()) ->postJson(route('api.accessories.checkout', Accessory::factory()->create())) @@ -85,7 +86,7 @@ class AccessoryCheckoutTest extends TestCase 'target_type' => User::class, 'item_id' => $accessory->id, 'item_type' => Accessory::class, - 'user_id' => $admin->id, + 'created_by' => $admin->id, ])->count(),'Log entry either does not exist or there are more than expected' ); } @@ -118,7 +119,7 @@ class AccessoryCheckoutTest extends TestCase 'target_type' => User::class, 'item_id' => $accessory->id, 'item_type' => Accessory::class, - 'user_id' => $admin->id, + 'created_by' => $admin->id, ])->count(), 'Log entry either does not exist or there are more than expected' ); @@ -180,7 +181,7 @@ class AccessoryCheckoutTest extends TestCase 'target_type' => User::class, 'item_id' => $accessory->id, 'item_type' => Accessory::class, - 'user_id' => $actor->id, + 'created_by' => $actor->id, 'note' => 'oh hi there', ])->count(), 'Log entry either does not exist or there are more than expected' diff --git a/tests/Feature/Checkouts/Api/ConsumableCheckoutTest.php b/tests/Feature/Checkouts/Api/ConsumableCheckoutTest.php index 3bd0212fda..94fa63cba3 100644 --- a/tests/Feature/Checkouts/Api/ConsumableCheckoutTest.php +++ b/tests/Feature/Checkouts/Api/ConsumableCheckoutTest.php @@ -84,7 +84,7 @@ class ConsumableCheckoutTest extends TestCase 'target_type' => User::class, 'item_id' => $consumable->id, 'item_type' => Consumable::class, - 'user_id' => $actor->id, + 'created_by' => $actor->id, 'note' => 'oh hi there', ])->count(), 'Log entry either does not exist or there are more than expected' diff --git a/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php b/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php index d4818ffc4b..f63a8471f2 100644 --- a/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php @@ -193,7 +193,7 @@ class AccessoryCheckoutTest extends TestCase 'target_type' => User::class, 'item_id' => $accessory->id, 'item_type' => Accessory::class, - 'user_id' => $actor->id, + 'created_by' => $actor->id, 'note' => 'oh hi there', ])->count(), 'Log entry either does not exist or there are more than expected' diff --git a/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php b/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php index 90132fcedf..484b7e72cb 100644 --- a/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php @@ -86,7 +86,7 @@ class ConsumableCheckoutTest extends TestCase 'target_type' => User::class, 'item_id' => $consumable->id, 'item_type' => Consumable::class, - 'user_id' => $actor->id, + 'created_by' => $actor->id, 'note' => 'oh hi there', ])->count(), 'Log entry either does not exist or there are more than expected' diff --git a/tests/Feature/Companies/Api/DeleteCompaniesTest.php b/tests/Feature/Companies/Api/DeleteCompaniesTest.php new file mode 100644 index 0000000000..3dcdb4fd21 --- /dev/null +++ b/tests/Feature/Companies/Api/DeleteCompaniesTest.php @@ -0,0 +1,56 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.companies.destroy', $company)) + ->assertForbidden(); + + $this->assertDatabaseHas('companies', ['id' => $company->id]); + } + + public function testCannotDeleteCompanyThatHasAssociatedItems() + { + $companyWithAssets = Company::factory()->hasAssets()->create(); + $companyWithAccessories = Company::factory()->hasAccessories()->create(); + $companyWithConsumables = Company::factory()->hasConsumables()->create(); + $companyWithComponents = Company::factory()->hasComponents()->create(); + $companyWithUsers = Company::factory()->hasUsers()->create(); + + $actor = $this->actingAsForApi(User::factory()->deleteCompanies()->create()); + + $actor->deleteJson(route('api.companies.destroy', $companyWithAssets))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithAccessories))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithConsumables))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithComponents))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.companies.destroy', $companyWithUsers))->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('companies', ['id' => $companyWithAssets->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithAccessories->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithConsumables->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithComponents->id]); + $this->assertDatabaseHas('companies', ['id' => $companyWithUsers->id]); + } + + public function testCanDeleteCompany() + { + $company = Company::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCompanies()->create()) + ->deleteJson(route('api.companies.destroy', $company)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('companies', ['id' => $company->id]); + } +} diff --git a/tests/Feature/Components/Api/DeleteComponentsTest.php b/tests/Feature/Components/Api/DeleteComponentsTest.php new file mode 100644 index 0000000000..e95fe34559 --- /dev/null +++ b/tests/Feature/Components/Api/DeleteComponentsTest.php @@ -0,0 +1,66 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.components.destroy', $component)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($component); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $componentA = Component::factory()->for($companyA)->create(); + $componentB = Component::factory()->for($companyB)->create(); + $componentC = Component::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteComponents()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteComponents()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.components.destroy', $componentB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.components.destroy', $componentA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.components.destroy', $componentC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($componentA); + $this->assertNotSoftDeleted($componentB); + $this->assertSoftDeleted($componentC); + } + + public function testCanDeleteComponents() + { + $component = Component::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteComponents()->create()) + ->deleteJson(route('api.components.destroy', $component)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($component); + } +} diff --git a/tests/Feature/Consumables/Api/DeleteConsumablesTest.php b/tests/Feature/Consumables/Api/DeleteConsumablesTest.php new file mode 100644 index 0000000000..1ab91e07a0 --- /dev/null +++ b/tests/Feature/Consumables/Api/DeleteConsumablesTest.php @@ -0,0 +1,66 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.consumables.destroy', $consumable)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($consumable); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $consumableA = Consumable::factory()->for($companyA)->create(); + $consumableB = Consumable::factory()->for($companyB)->create(); + $consumableC = Consumable::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteConsumables()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteConsumables()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.consumables.destroy', $consumableB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.consumables.destroy', $consumableA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.consumables.destroy', $consumableC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($consumableA); + $this->assertNotSoftDeleted($consumableB); + $this->assertSoftDeleted($consumableC); + } + + public function testCanDeleteConsumables() + { + $consumable = Consumable::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteConsumables()->create()) + ->deleteJson(route('api.consumables.destroy', $consumable)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($consumable); + } +} diff --git a/tests/Feature/CustomFields/Api/DeleteCustomFieldsTest.php b/tests/Feature/CustomFields/Api/DeleteCustomFieldsTest.php new file mode 100644 index 0000000000..ab40591e90 --- /dev/null +++ b/tests/Feature/CustomFields/Api/DeleteCustomFieldsTest.php @@ -0,0 +1,54 @@ +markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.customfields.destroy', $customField)) + ->assertForbidden(); + + $this->assertDatabaseHas('custom_fields', ['id' => $customField->id]); + } + + public function testCustomFieldsCannotBeDeletedIfTheyHaveAssociatedFieldsets() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + $customFieldset = CustomFieldset::factory()->create(); + + $customField->fieldset()->attach($customFieldset, ['order' => 1, 'required' => 'false']); + + $this->actingAsForApi(User::factory()->deleteCustomFields()->create()) + ->deleteJson(route('api.customfields.destroy', $customField)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('custom_fields', ['id' => $customField->id]); + } + + public function testCustomFieldsCanBeDeleted() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCustomFields()->create()) + ->deleteJson(route('api.customfields.destroy', $customField)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('custom_fields', ['id' => $customField->id]); + } +} diff --git a/tests/Feature/CustomFieldsets/Api/DeleteCustomFieldsetsTest.php b/tests/Feature/CustomFieldsets/Api/DeleteCustomFieldsetsTest.php new file mode 100644 index 0000000000..c464323653 --- /dev/null +++ b/tests/Feature/CustomFieldsets/Api/DeleteCustomFieldsetsTest.php @@ -0,0 +1,67 @@ +markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customFieldset = CustomFieldset::factory()->create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertForbidden(); + + $this->assertDatabaseHas('custom_fieldsets', ['id' => $customFieldset->id]); + } + + public function testCannotDeleteCustomFieldsetWithAssociatedFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customField = CustomField::factory()->create(); + $customFieldset = CustomFieldset::factory()->create(); + + $customField->fieldset()->attach($customFieldset, ['order' => 1, 'required' => 'false']); + + $this->actingAsForApi(User::factory()->deleteCustomFieldsets()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('custom_fieldsets', ['id' => $customFieldset->id]); + } + + public function testCannotDeleteCustomFieldsetWithAssociatedModels() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customFieldset = CustomFieldset::factory()->hasModels()->create(); + + $this->actingAsForApi(User::factory()->deleteCustomFieldsets()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('custom_fieldsets', ['id' => $customFieldset->id]); + } + + public function testCanDeleteCustomFieldsets() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + $customFieldset = CustomFieldset::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteCustomFieldsets()->create()) + ->deleteJson(route('api.fieldsets.destroy', $customFieldset)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('custom_fieldsets', ['id' => $customFieldset->id]); + } +} diff --git a/tests/Feature/Departments/Api/DeleteDepartmentsTest.php b/tests/Feature/Departments/Api/DeleteDepartmentsTest.php new file mode 100644 index 0000000000..cf59b81510 --- /dev/null +++ b/tests/Feature/Departments/Api/DeleteDepartmentsTest.php @@ -0,0 +1,77 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.departments.destroy', $department)) + ->assertForbidden(); + + $this->assertDatabaseHas('departments', ['id' => $department->id]); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $departmentA = Department::factory()->for($companyA)->create(); + $departmentB = Department::factory()->for($companyB)->create(); + $departmentC = Department::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteDepartments()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteDepartments()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.departments.destroy', $departmentB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.departments.destroy', $departmentA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.departments.destroy', $departmentC)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseHas('departments', ['id' => $departmentA->id]); + $this->assertDatabaseHas('departments', ['id' => $departmentB->id]); + $this->assertDatabaseMissing('departments', ['id' => $departmentC->id]); + } + + public function testCannotDeleteDepartmentThatStillHasUsers() + { + $department = Department::factory()->hasUsers()->create(); + + $this->actingAsForApi(User::factory()->deleteDepartments()->create()) + ->deleteJson(route('api.departments.destroy', $department)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('departments', ['id' => $department->id]); + } + + public function testCanDeleteDepartment() + { + $department = Department::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteDepartments()->create()) + ->deleteJson(route('api.departments.destroy', $department)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('departments', ['id' => $department->id]); + } +} diff --git a/tests/Feature/Depreciations/Api/DeleteDepreciationsTest.php b/tests/Feature/Depreciations/Api/DeleteDepreciationsTest.php new file mode 100644 index 0000000000..d1b32079cc --- /dev/null +++ b/tests/Feature/Depreciations/Api/DeleteDepreciationsTest.php @@ -0,0 +1,44 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.depreciations.destroy', $depreciation)) + ->assertForbidden(); + + $this->assertDatabaseHas('depreciations', ['id' => $depreciation->id]); + } + + public function testCannotDeleteDepreciationThatHasAssociatedModels() + { + $depreciation = Depreciation::factory()->hasModels()->create(); + + $this->actingAsForApi(User::factory()->deleteDepreciations()->create()) + ->deleteJson(route('api.depreciations.destroy', $depreciation)) + ->assertStatusMessageIs('error'); + + $this->assertDatabaseHas('depreciations', ['id' => $depreciation->id]); + } + + public function testCanDeleteDepreciation() + { + $depreciation = Depreciation::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteDepreciations()->create()) + ->deleteJson(route('api.depreciations.destroy', $depreciation)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('depreciations', ['id' => $depreciation->id]); + } +} diff --git a/tests/Feature/Groups/Api/DeleteGroupsTest.php b/tests/Feature/Groups/Api/DeleteGroupsTest.php new file mode 100644 index 0000000000..8259cec84c --- /dev/null +++ b/tests/Feature/Groups/Api/DeleteGroupsTest.php @@ -0,0 +1,34 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.groups.destroy', $group)) + ->assertForbidden(); + + $this->assertDatabaseHas('permission_groups', ['id' => $group->id]); + } + + public function testCanDeleteGroup() + { + $group = Group::factory()->create(); + + // only super admins can delete groups + $this->actingAsForApi(User::factory()->superuser()->create()) + ->deleteJson(route('api.groups.destroy', $group)) + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('permission_groups', ['id' => $group->id]); + } +} diff --git a/tests/Feature/Importing/Api/GeneralImportTest.php b/tests/Feature/Importing/Api/GeneralImportTest.php new file mode 100644 index 0000000000..5c38dab7f3 --- /dev/null +++ b/tests/Feature/Importing/Api/GeneralImportTest.php @@ -0,0 +1,16 @@ +actingAsForApi(User::factory()->canImport()->create()); + + $this->importFileResponse(['import' => 9999, 'import-type' => 'accessory']) + ->assertStatusMessageIs('import-errors'); + } +} diff --git a/tests/Feature/Importing/Api/ImportAccessoriesTest.php b/tests/Feature/Importing/Api/ImportAccessoriesTest.php new file mode 100644 index 0000000000..aac9dabb1d --- /dev/null +++ b/tests/Feature/Importing/Api/ImportAccessoriesTest.php @@ -0,0 +1,420 @@ +actingAsForApi(User::factory()->create()); + + $this->importFileResponse(['import' => 44])->assertForbidden(); + } + + #[Test] + public function userWithImportAccessoryPermissionCanImportAccessories(): void + { + $this->actingAsForApi(User::factory()->canImport()->create()); + + $import = Import::factory()->accessory()->create(); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function importAccessory(): void + { + $importFileBuilder = ImportFileBuilder::new(); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertOk() + ->assertExactJson([ + 'payload' => null, + 'status' => 'success', + 'messages' => [ + 'redirect_url' => route('accessories.index') + ] + ]); + + $newAccessory = Accessory::query() + ->with(['location', 'category', 'manufacturer', 'supplier', 'company']) + ->where('name', $row['itemName']) + ->sole(); + + $activityLog = Actionlog::query() + ->where('item_type', Accessory::class) + ->where('item_id', $newAccessory->id) + ->sole(); + + $this->assertEquals('create', $activityLog->action_type); + $this->assertEquals('importer', $activityLog->action_source); + $this->assertEquals($newAccessory->company->id, $activityLog->company_id); + + $this->assertEquals($row['itemName'], $newAccessory->name); + $this->assertEquals($row['quantity'], $newAccessory->qty); + $this->assertEquals($row['purchaseDate'], $newAccessory->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $newAccessory->purchase_cost); + $this->assertEquals($row['orderNumber'], $newAccessory->order_number); + $this->assertEquals($row['notes'], $newAccessory->notes); + $this->assertEquals($row['category'], $newAccessory->category->name); + $this->assertEquals('accessory', $newAccessory->category->category_type); + $this->assertEquals($row['manufacturerName'], $newAccessory->manufacturer->name); + $this->assertEquals($row['supplierName'], $newAccessory->supplier->name); + $this->assertEquals($row['location'], $newAccessory->location->name); + $this->assertEquals($row['companyName'], $newAccessory->company->name); + $this->assertEquals($row['modelNumber'], $newAccessory->model_number); + $this->assertFalse($newAccessory->requestable); + $this->assertNull($newAccessory->min_amt); + $this->assertNull($newAccessory->user_id); + } + + #[Test] + public function whenImportFileContainsUnknownColumns(): void + { + $row = ImportFileBuilder::new()->definition(); + $row['unknownColumn'] = $this->faker->word; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function willFormatDate(): void + { + $importFileBuilder = ImportFileBuilder::new(['purchaseDate' => '2022/10/10']); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $accessory = Accessory::query() + ->where('name', $importFileBuilder->firstRow()['itemName']) + ->sole(['purchase_date']); + + $this->assertEquals('2022-10-10', $accessory->purchase_date->toDateString()); + } + + #[Test] + public function willNotCreateNewCategoryWhenCategoryExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => Str::random()]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAccessories = Accessory::query() + ->whereIn('name', $importFileBuilder->pluck('itemName')) + ->get(); + + $this->assertCount(1, $newAccessories->pluck('category_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewAccessoryWhenAccessoryWithNameExists(): void + { + $accessory = Accessory::factory()->create(['name' => Str::random()]); + $importFileBuilder = ImportFileBuilder::times(2)->replace(['itemName' => $accessory->name]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $probablyNewAccessories = Accessory::query() + ->where('name', $importFileBuilder->pluck('itemName')) + ->get(['name']); + + $this->assertCount(1, $probablyNewAccessories); + $this->assertEquals($accessory->name, $probablyNewAccessories->first()->name); + } + + #[Test] + public function willNotCreateNewCompanyWhenCompanyAlreadyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAccessories = Accessory::query() + ->where('name', $importFileBuilder->pluck('itemName')) + ->get(['company_id']); + + $this->assertCount(1, $newAccessories->pluck('company_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewLocationWhenLocationAlreadyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAccessories = Accessory::query() + ->where('name', $importFileBuilder->pluck('itemName')) + ->get(['location_id']); + + $this->assertCount(1, $newAccessories->pluck('location_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewManufacturerWhenManufacturerAlreadyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['manufacturerName' => $this->faker->company]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAccessories = Accessory::query() + ->where('name', $importFileBuilder->pluck('itemName')) + ->get(['manufacturer_id']); + + $this->assertCount(1, $newAccessories->pluck('manufacturer_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewSupplierWhenSupplierAlreadyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['supplierName' => $this->faker->company]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAccessories = Accessory::query() + ->where('name', $importFileBuilder->pluck('itemName')) + ->get(['supplier_id']); + + $this->assertCount(1, $newAccessories->pluck('supplier_id')->unique()->all()); + } + + #[Test] + public function whenColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::new()->forget(['minimumAmount', 'purchaseCost', 'purchaseDate']); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAccessory = Accessory::query() + ->where('name', $importFileBuilder->firstRow()['itemName']) + ->sole(); + + $this->assertNull($newAccessory->min_amt); + $this->assertNull($newAccessory->purchase_date); + $this->assertNull($newAccessory->purchase_cost); + } + + #[Test] + public function whenRequiredColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::new()->forget(['itemName', 'quantity', 'category']); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + '' => [ + 'Accessory' => [ + 'name' => ['The name field is required.'], + 'qty' => ['The qty field must be at least 1.'], + 'category_id' => ['The category id field is required.'] + ] + ] + ] + ]); + } + + #[Test] + public function updateAccessoryFromImport(): void + { + $accessory = Accessory::factory()->create(['name' => Str::random()])->refresh(); + $importFileBuilder = ImportFileBuilder::new(['itemName' => $accessory->name]); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedAccessory = Accessory::query()->find($accessory->id); + $updatedAttributes = [ + 'name', 'company_id', 'qty', 'purchase_date', 'purchase_cost', + 'order_number', 'notes', 'category_id', 'manufacturer_id', 'supplier_id', + 'location_id', 'model_number', 'updated_at' + ]; + + $this->assertEquals($row['itemName'], $updatedAccessory->name); + $this->assertEquals($row['companyName'], $updatedAccessory->company->name); + $this->assertEquals($row['quantity'], $updatedAccessory->qty); + $this->assertEquals($row['purchaseDate'], $updatedAccessory->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $updatedAccessory->purchase_cost); + $this->assertEquals($row['orderNumber'], $updatedAccessory->order_number); + $this->assertEquals($row['notes'], $updatedAccessory->notes); + $this->assertEquals($row['category'], $updatedAccessory->category->name); + $this->assertEquals('accessory', $updatedAccessory->category->category_type); + $this->assertEquals($row['manufacturerName'], $updatedAccessory->manufacturer->name); + $this->assertEquals($row['supplierName'], $updatedAccessory->supplier->name); + $this->assertEquals($row['location'], $updatedAccessory->location->name); + $this->assertEquals($row['modelNumber'], $updatedAccessory->model_number); + + $this->assertEquals( + Arr::except($accessory->attributesToArray(), $updatedAttributes), + Arr::except($updatedAccessory->attributesToArray(), $updatedAttributes), + ); + } + + #[Test] + public function whenImportFileContainsEmptyValues(): void + { + $accessory = Accessory::factory()->create(['name' => Str::random()]); + $accessory->refresh(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => ' ', + 'purchaseDate' => ' ', + 'purchaseCost' => '', + 'location' => '', + 'companyName' => '', + 'orderNumber' => '', + 'category' => '', + 'quantity' => '', + 'manufacturerName' => '', + 'supplierName' => '', + 'notes' => '', + 'requestAble' => '', + 'minimumAmount' => '', + 'modelNumber' => '' + ]); + + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + $importFileBuilder->firstRow()['itemName'] => [ + 'Accessory' => [ + 'qty' => ['The qty field must be at least 1.'], + 'category_id' => ['The category id field is required.'] + ] + ] + ] + ]); + + $importFileBuilder->replace(['itemName' => $accessory->name]); + + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedAccessory = clone $accessory; + $updatedAccessory->refresh(); + + $this->assertEquals($accessory->toArray(), $updatedAccessory->toArray()); + } + + #[Test] + public function customColumnMapping(): void + { + $faker = ImportFileBuilder::new()->definition(); + $row = [ + 'itemName' => $faker['modelNumber'], + 'purchaseDate' => $faker['notes'], + 'purchaseCost' => $faker['location'], + 'location' => $faker['purchaseCost'], + 'companyName' => $faker['orderNumber'], + 'orderNumber' => $faker['companyName'], + 'category' => $faker['manufacturerName'], + 'manufacturerName' => $faker['category'], + 'notes' => $faker['purchaseDate'], + 'minimumAmount' => $faker['supplierName'], + 'modelNumber' => $faker['itemName'], + 'quantity' => $faker['quantity'] + ]; + + $importFileBuilder = new ImportFileBuilder([$row]); + $import = Import::factory()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse([ + 'import' => $import->id, + 'column-mappings' => [ + 'Item Name' => 'model_number', + 'Purchase Date' => 'notes', + 'Purchase Cost' => 'location', + 'Location' => 'purchase_cost', + 'Company' => 'order_number', + 'Order Number' => 'company', + 'Category' => 'manufacturer', + 'Manufacturer' => 'category', + 'Supplier' => 'min_amt', + 'Notes' => 'purchase_date', + 'Min QTY' => 'supplier', + 'Model Number' => 'item_name', + 'Quantity' => 'quantity' + ] + ])->assertOk(); + + $newAccessory = Accessory::query() + ->with(['location', 'category', 'manufacturer', 'supplier']) + ->where('name', $row['modelNumber']) + ->sole(); + + $this->assertEquals($row['modelNumber'], $newAccessory->name); + $this->assertEquals($row['itemName'], $newAccessory->model_number); + $this->assertEquals($row['quantity'], $newAccessory->qty); + $this->assertEquals($row['notes'], $newAccessory->purchase_date->toDateString()); + $this->assertEquals($row['location'], $newAccessory->purchase_cost); + $this->assertEquals($row['companyName'], $newAccessory->order_number); + $this->assertEquals($row['purchaseDate'], $newAccessory->notes); + $this->assertEquals($row['manufacturerName'], $newAccessory->category->name); + $this->assertEquals($row['category'], $newAccessory->manufacturer->name); + $this->assertEquals($row['purchaseCost'], $newAccessory->location->name); + } +} diff --git a/tests/Feature/Importing/Api/ImportAssetsTest.php b/tests/Feature/Importing/Api/ImportAssetsTest.php new file mode 100644 index 0000000000..e001add381 --- /dev/null +++ b/tests/Feature/Importing/Api/ImportAssetsTest.php @@ -0,0 +1,595 @@ +actingAsForApi(User::factory()->create()); + + $this->importFileResponse(['import' => 44])->assertForbidden(); + } + + #[Test] + public function userWithImportAssetsPermissionCanImportAssets(): void + { + $this->actingAsForApi(User::factory()->canImport()->create()); + + $import = Import::factory()->asset()->create(); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function importAsset(): void + { + Notification::fake(); + + $importFileBuilder = ImportFileBuilder::new(); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertOk() + ->assertExactJson([ + 'payload' => null, + 'status' => 'success', + 'messages' => ['redirect_url' => route('hardware.index')] + ]); + + $newAsset = Asset::query() + ->with(['location', 'supplier', 'company', 'assignedAssets', 'defaultLoc', 'assetStatus', 'model.category', 'model.manufacturer']) + ->where('serial', $row['serialNumber']) + ->sole(); + + $assignee = User::query()->find($newAsset->assigned_to, ['id', 'first_name', 'last_name', 'email', 'username']); + + $activityLogs = ActionLog::query() + ->where('item_type', Asset::class) + ->where('item_id', $newAsset->id) + ->get(); + + $this->assertCount(2, $activityLogs); + + $this->assertEquals('checkout', $activityLogs[0]->action_type); + $this->assertEquals(Asset::class, $activityLogs[0]->item_type); + $this->assertEquals($assignee->id, $activityLogs[0]->target_id); + $this->assertEquals(User::class, $activityLogs[0]->target_type); + $this->assertEquals('Checkout from CSV Importer', $activityLogs[0]->note); + + $this->assertEquals('create', $activityLogs[1]->action_type); + $this->assertNull($activityLogs[1]->target_id); + $this->assertEquals(Asset::class, $activityLogs[1]->item_type); + $this->assertNull($activityLogs[1]->note); + $this->assertNull($activityLogs[1]->target_type); + + $this->assertEquals($row['assigneeFullName'], "{$assignee->first_name} {$assignee->last_name}"); + $this->assertEquals($row['assigneeEmail'], $assignee->email); + $this->assertEquals($row['assigneeUsername'], $assignee->username); + + $this->assertEquals($row['category'], $newAsset->model->category->name); + $this->assertEquals($row['manufacturerName'], $newAsset->model->manufacturer->name); + $this->assertEquals($row['itemName'], $newAsset->name); + $this->assertEquals($row['tag'], $newAsset->asset_tag); + $this->assertEquals($row['model'], $newAsset->model->name); + $this->assertEquals($row['modelNumber'], $newAsset->model->model_number); + $this->assertEquals($row['purchaseDate'], $newAsset->purchase_date->toDateString()); + $this->assertNull($newAsset->asset_eol_date); + $this->assertEquals(0, $newAsset->eol_explicit); + $this->assertEquals($newAsset->location_id, $newAsset->rtd_location_id); + $this->assertEquals($row['purchaseCost'], $newAsset->purchase_cost); + $this->assertNull($newAsset->order_number); + $this->assertEquals('', $newAsset->image); + $this->assertNull($newAsset->user_id); + $this->assertEquals(1, $newAsset->physical); + $this->assertEquals($row['status'], $newAsset->assetStatus->name); + $this->assertEquals(0, $newAsset->archived); + $this->assertEquals($row['warrantyInMonths'], $newAsset->warranty_months); + $this->assertNull($newAsset->deprecate); + $this->assertEquals($row['supplierName'], $newAsset->supplier->name); + $this->assertEquals(0, $newAsset->requestable); + $this->assertEquals($row['location'], $newAsset->defaultLoc->name); + $this->assertEquals(null, $newAsset->accepted); + $this->assertEquals(now()->toDateString(), Carbon::parse($newAsset->last_checkout)->toDateString()); + $this->assertEquals(0, $newAsset->last_checkin); + $this->assertEquals(0, $newAsset->expected_checkin); + $this->assertEquals($row['companyName'], $newAsset->company->name); + $this->assertEquals(User::class, $newAsset->assigned_type); + $this->assertNull($newAsset->last_audit_date); + $this->assertNull($newAsset->next_audit_date); + $this->assertEquals($row['location'], $newAsset->location->name); + $this->assertEquals(0, $newAsset->checkin_counter); + $this->assertEquals(1, $newAsset->checkout_counter); + $this->assertEquals(0, $newAsset->requests_counter); + $this->assertEquals(0, $newAsset->byod); + + //Notes is never read. + // $this->assertEquals($row['notes'], $newAsset->notes); + + Notification::assertSentTo($assignee, CheckoutAssetNotification::class); + } + + #[Test] + public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void + { + $row = ImportFileBuilder::new()->definition(); + $row['unknownColumnInCsvFile'] = 'foo'; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function willNotCreateNewAssetWhenAssetWithSameTagAlreadyExists(): void + { + $asset = Asset::factory()->create(['asset_tag' => $this->faker->uuid]); + $importFileBuilder = ImportFileBuilder::times(4)->replace(['tag' => $asset->asset_tag]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + '' => [ + 'asset_tag' => [ + 'asset_tag' => [ + "An asset with the asset tag {$asset->asset_tag} already exists and an update was not requested. No change was made." + ] + ] + ] + ] + ]); + + $assetsWithSameTag = Asset::query()->where('asset_tag', $asset->asset_tag)->get(); + + $this->assertCount(1, $assetsWithSameTag); + } + + #[Test] + public function willNotCreateNewCompanyWhenCompanyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAssets = Asset::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(); + + $this->assertCount(1, $newAssets->pluck('company_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewLocationWhenLocationExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAssets = Asset::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(); + + $this->assertCount(1, $newAssets->pluck('location_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewSupplierWhenSupplierExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['supplierName' => $this->faker->company]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAssets = Asset::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['supplier_id']); + + $this->assertCount(1, $newAssets->pluck('supplier_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewManufacturerWhenManufacturerExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['manufacturerName' => $this->faker->company]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAssets = Asset::query() + ->with('model.manufacturer') + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(); + + $this->assertCount(1, $newAssets->pluck('model.manufacturer_id')->unique()->all()); + } + + #[Test] + public function willNotCreateCategoryWhenCategoryExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => $this->faker->company]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAssets = Asset::query() + ->with('model.category') + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(); + + $this->assertCount(1, $newAssets->pluck('model.category_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewAssetModelWhenAssetModelExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['model' => Str::random()]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAssets = Asset::query() + ->with('model') + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(); + + $this->assertCount(1, $newAssets->pluck('model.name')->unique()->all()); + } + + #[Test] + public function whenColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::times()->forget([ + 'purchaseCost', + 'purchaseDate', + 'status' + ]); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::query() + ->with(['assetStatus']) + ->where('serial', $importFileBuilder->firstRow()['serialNumber']) + ->sole(); + + $this->assertEquals('Ready to Deploy', $newAsset->assetStatus->name); + $this->assertNull($newAsset->purchase_date); + $this->assertNull($newAsset->purchase_cost); + } + + #[Test] + public function willFormatValues(): void + { + $importFileBuilder = ImportFileBuilder::new([ + 'warrantyInMonths' => '3 months', + 'purchaseDate' => '2022/10/10' + ]); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::query() + ->where('serial', $importFileBuilder->firstRow()['serialNumber']) + ->sole(); + + $this->assertEquals(3, $newAsset->warranty_months); + $this->assertEquals('2022-10-10', $newAsset->purchase_date->toDateString()); + } + + #[Test] + public function whenRequiredColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::times(2) + ->forget(['tag']) + ->replace(['model' => '']); + + $rows = $importFileBuilder->all(); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + $rows[0]['itemName'] => [ + "Asset \"{$rows[0]['itemName']}\"" => [ + 'asset_tag' => [ + 'The asset tag field must be at least 1 characters.', + ], + 'model_id' => [ + 'The model id field is required.' + ] + ] + ], + $rows[1]['itemName'] => [ + "Asset \"{$rows[1]['itemName']}\"" => [ + 'asset_tag' => [ + 'The asset tag field must be at least 1 characters.', + ], + 'model_id' => [ + 'The model id field is required.' + ] + ] + ] + ] + ]); + + $newAssets = Asset::query() + ->whereIn('serial', Arr::pluck($rows, 'serialNumber')) + ->get(); + + $this->assertCount(0, $newAssets); + } + + #[Test] + public function updateAssetFromImport(): void + { + $asset = Asset::factory()->create()->refresh(); + $importFileBuilder = ImportFileBuilder::times(1)->replace(['tag' => $asset->asset_tag]); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedAsset = Asset::query() + ->with(['location', 'supplier', 'company', 'defaultLoc', 'assetStatus', 'model.category', 'model.manufacturer']) + ->find($asset->id); + + $assignee = User::query()->find($updatedAsset->assigned_to, ['id', 'first_name', 'last_name', 'email', 'username']); + + $updatedAttributes = [ + 'category', 'manufacturer_id', 'name', 'tag', 'model_id', + 'model_number', 'purchase_date', 'purchase_cost', 'warranty_months', 'supplier_id', + 'location_id', 'company_id', 'serial', 'assigned_to', 'status_id', 'rtd_location_id', + 'last_checkout', 'requestable', 'updated_at', 'checkout_counter', 'assigned_type' + ]; + + $this->assertEquals($row['assigneeFullName'], "{$assignee->first_name} {$assignee->last_name}"); + $this->assertEquals($row['assigneeEmail'], $assignee->email); + $this->assertEquals($row['assigneeUsername'], $assignee->username); + + $this->assertEquals($row['category'], $updatedAsset->model->category->name); + $this->assertEquals($row['manufacturerName'], $updatedAsset->model->manufacturer->name); + $this->assertEquals($row['itemName'], $updatedAsset->name); + $this->assertEquals($row['tag'], $updatedAsset->asset_tag); + $this->assertEquals($row['model'], $updatedAsset->model->name); + $this->assertEquals($row['modelNumber'], $updatedAsset->model->model_number); + $this->assertEquals($row['purchaseDate'], $updatedAsset->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $updatedAsset->purchase_cost); + $this->assertEquals($row['status'], $updatedAsset->assetStatus->name); + $this->assertEquals($row['warrantyInMonths'], $updatedAsset->warranty_months); + $this->assertEquals($row['supplierName'], $updatedAsset->supplier->name); + $this->assertEquals($row['location'], $updatedAsset->defaultLoc->name); + $this->assertEquals($row['companyName'], $updatedAsset->company->name); + $this->assertEquals($row['location'], $updatedAsset->location->name); + $this->assertEquals(1, $updatedAsset->checkout_counter); + $this->assertEquals(user::class, $updatedAsset->assigned_type); + + //RequestAble is always updated regardless of initial value. + // $this->assertEquals($asset->requestable, $updatedAsset->requestable); + + $this->assertEquals( + Arr::except($asset->attributesToArray(), $updatedAttributes), + Arr::except($updatedAsset->attributesToArray(), $updatedAttributes), + ); + } + + #[Test] + public function customColumnMapping(): void + { + $faker = ImportFileBuilder::new()->definition(); + $row = [ + 'assigneeFullName' => $faker['supplierName'], + 'assigneeEmail' => $faker['manufacturerName'], + 'assigneeUsername' => $faker['serialNumber'], + 'category' => $faker['location'], + 'companyName' => $faker['purchaseCost'], + 'itemName' => $faker['modelNumber'], + 'location' => $faker['assigneeUsername'], + 'manufacturerName' => $faker['status'], + 'model' => $faker['itemName'], + 'modelNumber' => $faker['category'], + 'notes' => $faker['notes'], + 'purchaseCost' => $faker['model'], + 'purchaseDate' => $faker['companyName'], + 'serialNumber' => $faker['tag'], + 'supplierName' => $faker['purchaseDate'], + 'status' => $faker['warrantyInMonths'], + 'tag' => $faker['assigneeEmail'], + 'warrantyInMonths' => $faker['assigneeFullName'], + ]; + + $importFileBuilder = new ImportFileBuilder([$row]); + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse([ + 'import' => $import->id, + 'column-mappings' => [ + 'Asset Tag' => 'email', + 'Category' => 'location', + 'Company' => 'purchase_cost', + 'Email' => 'manufacturer', + 'Full Name' => 'supplier', + 'Item Name' => 'model_number', + 'Location' => 'username', + 'Manufacturer' => 'status', + 'Model name' => 'item_name', + 'Model Number' => 'category', + 'Notes' => 'asset_notes', + 'Purchase Cost' => 'asset_model', + 'Purchase Date' => 'company', + 'Serial number' => 'asset_tag', + 'Status' => 'warranty_months', + 'Supplier' => 'purchase_date', + 'Username' => 'serial', + 'Warranty' => 'full_name', + ] + ])->assertOk(); + + $asset = Asset::query() + ->with(['location', 'supplier', 'company', 'assignedAssets', 'defaultLoc', 'assetStatus', 'model.category', 'model.manufacturer']) + ->where('serial', $row['assigneeUsername']) + ->sole(); + + $assignee = User::query()->find($asset->assigned_to, ['id', 'first_name', 'last_name', 'email', 'username']); + + $this->assertEquals($row['warrantyInMonths'], "{$assignee->first_name} {$assignee->last_name}"); + $this->assertEquals($row['tag'], $assignee->email); + $this->assertEquals($row['location'], $assignee->username); + + $this->assertEquals($row['modelNumber'], $asset->model->category->name); + $this->assertEquals($row['assigneeEmail'], $asset->model->manufacturer->name); + $this->assertEquals($row['model'], $asset->name); + $this->assertEquals($row['serialNumber'], $asset->asset_tag); + $this->assertEquals($row['purchaseCost'], $asset->model->name); + $this->assertEquals($row['itemName'], $asset->model->model_number); + $this->assertEquals($row['supplierName'], $asset->purchase_date->toDateString()); + $this->assertEquals($row['companyName'], $asset->purchase_cost); + $this->assertEquals($row['manufacturerName'], $asset->assetStatus->name); + $this->assertEquals($row['status'], $asset->warranty_months); + $this->assertEquals($row['assigneeFullName'], $asset->supplier->name); + $this->assertEquals($row['category'], $asset->defaultLoc->name); + $this->assertEquals($row['purchaseDate'], $asset->company->name); + $this->assertEquals($row['category'], $asset->location->name); + $this->assertEquals($row['notes'], $asset->notes); + $this->assertNull($asset->asset_eol_date); + $this->assertEquals(0, $asset->eol_explicit); + $this->assertNull($asset->order_number); + $this->assertEquals('', $asset->image); + $this->assertNull($asset->user_id); + $this->assertEquals(1, $asset->physical); + $this->assertEquals(0, $asset->archived); + $this->assertNull($asset->deprecate); + $this->assertEquals(0, $asset->requestable); + $this->assertEquals(null, $asset->accepted); + $this->assertEquals(now()->toDateString(), Carbon::parse($asset->last_checkout)->toDateString()); + $this->assertEquals(0, $asset->last_checkin); + $this->assertEquals(0, $asset->expected_checkin); + $this->assertEquals(User::class, $asset->assigned_type); + $this->assertNull($asset->last_audit_date); + $this->assertNull($asset->next_audit_date); + $this->assertEquals(0, $asset->checkin_counter); + $this->assertEquals(1, $asset->checkout_counter); + $this->assertEquals(0, $asset->requests_counter); + $this->assertEquals(0, $asset->byod); + } + + #[Test] + public function customFields(): void + { + $macAddress = $this->faker->macAddress; + + $row = ImportFileBuilder::new()->definition(); + $row['Mac Address'] = $macAddress; + + $importFileBuilder = new ImportFileBuilder([$row]); + $customField = CustomField::query()->where('name', 'Mac Address')->firstOrNew(); + + if (!$customField->exists) { + $customField = CustomField::factory()->macAddress()->create(['db_column' => '_snipeit_mac_address_1']); + } + + if ($customField->field_encrypted) { + $customField->field_encrypted = 0; + $customField->save(); + } + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::query()->where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + + $this->assertEquals($macAddress, $newAsset->getAttribute($customField->db_column)); + } + + #[Test] + public function willEncryptCustomFields(): void + { + $macAddress = $this->faker->macAddress; + $row = ImportFileBuilder::new()->definition(); + + $row['Mac Address'] = $macAddress; + + $importFileBuilder = new ImportFileBuilder([$row]); + $customField = CustomField::query()->where('name', 'Mac Address')->firstOrNew(); + + if (!$customField->exists) { + $customField = CustomField::factory()->macAddress()->create(); + } + + if (!$customField->field_encrypted) { + $customField->field_encrypted = 1; + $customField->save(); + } + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $asset = Asset::query()->where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $encryptedMacAddress = $asset->getAttribute($customField->db_column); + + $this->assertNotEquals($encryptedMacAddress, $macAddress); + } +} diff --git a/tests/Feature/Importing/Api/ImportComponentsTest.php b/tests/Feature/Importing/Api/ImportComponentsTest.php new file mode 100644 index 0000000000..376b5b32eb --- /dev/null +++ b/tests/Feature/Importing/Api/ImportComponentsTest.php @@ -0,0 +1,305 @@ +actingAsForApi(User::factory()->create()); + + $this->importFileResponse(['import' => 44])->assertForbidden(); + } + + #[Test] + public function userWithImportAssetsPermissionCanImportComponents(): void + { + $this->actingAsForApi(User::factory()->canImport()->create()); + + $import = Import::factory()->component()->create(); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function importComponents(): void + { + Notification::fake(); + + $importFileBuilder = ImportFileBuilder::new(); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertOk() + ->assertExactJson([ + 'payload' => null, + 'status' => 'success', + 'messages' => ['redirect_url' => route('components.index')] + ]); + + $newComponent = Component::query() + ->with(['location', 'category', 'company']) + ->where('name', $row['itemName']) + ->sole(); + + $activityLog = ActionLog::query() + ->where('item_type', Component::class) + ->where('item_id', $newComponent->id) + ->sole(); + + $this->assertEquals('create', $activityLog->action_type); + $this->assertEquals('importer', $activityLog->action_source); + $this->assertEquals($newComponent->company->id, $activityLog->company_id); + + $this->assertEquals($row['itemName'], $newComponent->name); + $this->assertEquals($row['companyName'], $newComponent->company->name); + $this->assertEquals($row['category'], $newComponent->category->name); + $this->assertEquals($row['location'], $newComponent->location->name); + $this->assertNull($newComponent->supplier_id); + $this->assertEquals($row['quantity'], $newComponent->qty); + $this->assertEquals($row['orderNumber'], $newComponent->order_number); + $this->assertEquals($row['purchaseDate'], $newComponent->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $newComponent->purchase_cost); + $this->assertNull($newComponent->min_amt); + $this->assertEquals($row['serialNumber'], $newComponent->serial); + $this->assertNull($newComponent->image); + $this->assertNull($newComponent->notes); + } + + #[Test] + public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void + { + $row = ImportFileBuilder::new()->firstRow(); + $row['unknownColumnInCsvFile'] = 'foo'; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function willNotCreateNewComponentWhenComponentWithNameAndSerialNumberExists(): void + { + $component = Component::factory()->create(); + + $importFileBuilder = ImportFileBuilder::times(4)->replace([ + 'itemName' => $component->name, + 'serialNumber' => $component->serial + ]); + + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $probablyNewComponents = Component::query() + ->where('name', $component->name) + ->where('serial', $component->serial) + ->get(['id']); + + $this->assertCount(1, $probablyNewComponents); + $this->assertEquals($component->id, $probablyNewComponents->sole()->id); + } + + #[Test] + public function willNotCreateNewCompanyWhenCompanyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponents = Component::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['company_id']); + + $this->assertCount(1, $newComponents->pluck('company_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewLocationWhenLocationExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponents = Component::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['location_id']); + + $this->assertCount(1, $newComponents->pluck('location_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewCategoryWhenCategoryExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => $this->faker->company]); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponents = Component::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['category_id']); + + $this->assertCount(1, $newComponents->pluck('category_id')->unique()->all()); + } + + #[Test] + public function whenRequiredColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::new() + ->replace(['category' => '']) + ->forget(['quantity']); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + $row['itemName'] => [ + 'Component' => [ + 'qty' => ['The qty field must be at least 1.'], + 'category_id' => ['The category id field is required.'] + ] + ] + ] + ]); + + $newComponents = Component::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(); + + $this->assertCount(0, $newComponents); + } + + #[Test] + public function updateComponentFromImport(): void + { + $component = Component::factory()->create(); + $importFileBuilder = ImportFileBuilder::new([ + 'itemName' => $component->name, + 'serialNumber' => $component->serial + ]); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedComponent = Component::query() + ->with(['location', 'category']) + ->where('serial', $row['serialNumber']) + ->sole(); + + $this->assertEquals($row['itemName'], $updatedComponent->name); + $this->assertEquals($row['category'], $updatedComponent->category->name); + $this->assertEquals($row['location'], $updatedComponent->location->name); + $this->assertEquals($component->supplier_id, $updatedComponent->supplier_id); + $this->assertEquals($row['quantity'], $updatedComponent->qty); + $this->assertEquals($row['orderNumber'], $updatedComponent->order_number); + $this->assertEquals($row['purchaseDate'], $updatedComponent->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $updatedComponent->purchase_cost); + $this->assertEquals($component->min_amt, $updatedComponent->min_amt); + $this->assertEquals($row['serialNumber'], $updatedComponent->serial); + $this->assertEquals($component->image, $updatedComponent->image); + $this->assertEquals($component->notes, $updatedComponent->notes); + } + + #[Test] + public function customColumnMapping(): void + { + $faker = ImportFileBuilder::new()->definition(); + $row = [ + 'category' => $faker['serialNumber'], + 'companyName' => $faker['quantity'], + 'itemName' => $faker['purchaseDate'], + 'location' => $faker['purchaseCost'], + 'orderNumber' => $faker['orderNumber'], + 'purchaseCost' => $faker['category'], + 'purchaseDate' => $faker['companyName'], + 'quantity' => $faker['itemName'], + 'serialNumber' => $faker['location'] + ]; + + $importFileBuilder = new ImportFileBuilder([$row]); + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse([ + 'import' => $import->id, + 'column-mappings' => [ + 'Category' => 'serial', + 'Company' => 'quantity', + 'item Name' => 'purchase_date', + 'Location' => 'purchase_cost', + 'Order Number' => 'order_number', + 'Purchase Cost' => 'category', + 'Purchase Date' => 'company', + 'Quantity' => 'item_name', + 'Serial number' => 'location', + ] + ])->assertOk(); + + $newComponent = Component::query() + ->with(['location', 'category']) + ->where('serial', $importFileBuilder->firstRow()['category']) + ->sole(); + + $this->assertEquals($row['quantity'], $newComponent->name); + $this->assertEquals($row['purchaseCost'], $newComponent->category->name); + $this->assertEquals($row['serialNumber'], $newComponent->location->name); + $this->assertNull($newComponent->supplier_id); + $this->assertEquals($row['companyName'], $newComponent->qty); + $this->assertEquals($row['orderNumber'], $newComponent->order_number); + $this->assertEquals($row['itemName'], $newComponent->purchase_date->toDateString()); + $this->assertEquals($row['location'], $newComponent->purchase_cost); + $this->assertNull($newComponent->min_amt); + $this->assertNull($newComponent->image); + $this->assertNull($newComponent->notes); + } +} diff --git a/tests/Feature/Importing/Api/ImportConsumablesTest.php b/tests/Feature/Importing/Api/ImportConsumablesTest.php new file mode 100644 index 0000000000..81bfdf4bf8 --- /dev/null +++ b/tests/Feature/Importing/Api/ImportConsumablesTest.php @@ -0,0 +1,305 @@ +actingAsForApi(User::factory()->create()); + + $this->importFileResponse(['import' => 44])->assertForbidden(); + } + + #[Test] + public function userWithImportAssetsPermissionCanImportConsumables(): void + { + $this->actingAsForApi(User::factory()->canImport()->create()); + + $import = Import::factory()->consumable()->create(); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function importConsumables(): void + { + Notification::fake(); + + $importFileBuilder = ImportFileBuilder::new(); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertOk() + ->assertExactJson([ + 'payload' => null, + 'status' => 'success', + 'messages' => ['redirect_url' => route('consumables.index')] + ]); + + $newConsumable = Consumable::query() + ->with(['location', 'category', 'company']) + ->where('name', $row['itemName']) + ->sole(); + + $activityLog = ActivityLog::query() + ->where('item_type', Consumable::class) + ->where('item_id', $newConsumable->id) + ->sole(); + + $this->assertEquals('create', $activityLog->action_type); + $this->assertEquals('importer', $activityLog->action_source); + $this->assertEquals($newConsumable->company->id, $activityLog->company_id); + + $this->assertEquals($row['itemName'], $newConsumable->name); + $this->assertEquals($row['category'], $newConsumable->category->name); + $this->assertEquals($row['location'], $newConsumable->location->name); + $this->assertEquals($row['companyName'], $newConsumable->company->name); + $this->assertNull($newConsumable->supplier_id); + $this->assertFalse($newConsumable->requestable); + $this->assertNull($newConsumable->image); + $this->assertEquals($row['orderNumber'], $newConsumable->order_number); + $this->assertEquals($row['purchaseDate'], $newConsumable->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $newConsumable->purchase_cost); + $this->assertNull($newConsumable->min_amt); + $this->assertEquals('', $newConsumable->model_number); + $this->assertNull($newConsumable->item_number); + $this->assertNull($newConsumable->manufacturer_id); + $this->assertNull($newConsumable->notes); + } + + #[Test] + public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void + { + $row = ImportFileBuilder::new()->definition(); + $row['unknownColumnInCsvFile'] = 'foo'; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function willNotCreateNewConsumableWhenConsumableNameAlreadyExist(): void + { + $consumable = Consumable::factory()->create(['name' => Str::random()]); + $importFileBuilder = ImportFileBuilder::new(['itemName' => $consumable->name]); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $probablyNewConsumables = Consumable::query() + ->where('name', $consumable->name) + ->get(); + + $this->assertCount(1, $probablyNewConsumables); + $this->assertEquals($consumable->id, $probablyNewConsumables->sole()->id); + } + + #[Test] + public function willNotCreateNewCompanyWhenCompanyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newConsumables = Consumable::query() + ->whereIn('name', $importFileBuilder->pluck('itemName')) + ->get(['company_id']); + + $this->assertCount(1, $newConsumables->pluck('company_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewLocationWhenLocationExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newConsumables = Consumable::query() + ->whereIn('name', $importFileBuilder->pluck('itemName')) + ->get(['location_id']); + + $this->assertCount(1, $newConsumables->pluck('location_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewCategoryWhenCategoryExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => Str::random()]); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newConsumables = Consumable::query() + ->whereIn('name', $importFileBuilder->pluck('itemName')) + ->get(['category_id']); + + $this->assertCount(1, $newConsumables->pluck('category_id')->unique()->all()); + } + + #[Test] + public function whenRequiredColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::new(['category' => ''])->forget(['quantity', 'name']); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + $row['itemName'] => [ + 'Consumable' => [ + 'category_id' => ['The category id field is required.'] + ] + ] + ] + ]); + + $newConsumables = Consumable::query() + ->whereIn('name', $importFileBuilder->pluck('itemName')) + ->get(['id']); + + $this->assertCount(0, $newConsumables); + } + + #[Test] + public function updateConsumableFromImport(): void + { + $consumable = Consumable::factory()->create(['name' => Str::random()]); + $importFileBuilder = ImportFileBuilder::new(['itemName' => $consumable->name]); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedConsumable = Consumable::query() + ->with(['location', 'category', 'company']) + ->where('name', $importFileBuilder->firstRow()['itemName']) + ->sole(); + + $this->assertEquals($row['itemName'], $updatedConsumable->name); + $this->assertEquals($row['category'], $updatedConsumable->category->name); + $this->assertEquals($row['location'], $updatedConsumable->location->name); + $this->assertEquals($row['companyName'], $updatedConsumable->company->name); + $this->assertEquals($row['orderNumber'], $updatedConsumable->order_number); + $this->assertEquals($row['purchaseDate'], $updatedConsumable->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $updatedConsumable->purchase_cost); + + $this->assertEquals($consumable->supplier_id, $updatedConsumable->supplier_id); + $this->assertEquals($consumable->requestable, $updatedConsumable->requestable); + $this->assertEquals($consumable->min_amt, $updatedConsumable->min_amt); + $this->assertEquals($consumable->model_number, $updatedConsumable->model_number); + $this->assertEquals($consumable->item_number, $updatedConsumable->item_number); + $this->assertEquals($consumable->manufacturer_id, $updatedConsumable->manufacturer_id); + $this->assertEquals($consumable->notes, $updatedConsumable->notes); + $this->assertEquals($consumable->item_number, $updatedConsumable->item_number); + } + + #[Test] + public function customColumnMapping(): void + { + $faker = ImportFileBuilder::new()->definition(); + $row = [ + 'category' => $faker['supplier'], + 'companyName' => $faker['quantity'], + 'itemName' => $faker['purchaseDate'], + 'location' => $faker['purchaseCost'], + 'orderNumber' => $faker['orderNumber'], + 'purchaseCost' => $faker['location'], + 'purchaseDate' => $faker['companyName'], + 'quantity' => $faker['itemName'], + 'supplier' => $faker['category'] + ]; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $import = Import::factory()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse([ + 'import' => $import->id, + 'column-mappings' => [ + 'Category' => 'supplier', + 'Company' => 'quantity', + 'item Name' => 'purchase_date', + 'Location' => 'purchase_cost', + 'Order Number' => 'order_number', + 'Purchase Cost' => 'location', + 'Purchase Date' => 'company', + 'Quantity' => 'item_name', + 'Supplier' => 'category', + ] + ])->assertOk(); + + $newConsumable = Consumable::query() + ->with(['location', 'category', 'company']) + ->where('name', $importFileBuilder->firstRow()['quantity']) + ->sole(); + + $this->assertEquals($row['supplier'], $newConsumable->category->name); + $this->assertEquals($row['purchaseCost'], $newConsumable->location->name); + $this->assertEquals($row['purchaseDate'], $newConsumable->company->name); + $this->assertEquals($row['companyName'], $newConsumable->qty); + $this->assertEquals($row['quantity'], $newConsumable->name); + $this->assertNull($newConsumable->supplier_id); + $this->assertFalse($newConsumable->requestable); + $this->assertNull($newConsumable->image); + $this->assertEquals($row['orderNumber'], $newConsumable->order_number); + $this->assertEquals($row['itemName'], $newConsumable->purchase_date->toDateString()); + $this->assertEquals($row['location'], $newConsumable->purchase_cost); + $this->assertNull($newConsumable->min_amt); + $this->assertEquals('', $newConsumable->model_number); + $this->assertNull($newConsumable->item_number); + $this->assertNull($newConsumable->manufacturer_id); + $this->assertNull($newConsumable->notes); + } +} diff --git a/tests/Feature/Importing/Api/ImportDataTestCase.php b/tests/Feature/Importing/Api/ImportDataTestCase.php new file mode 100644 index 0000000000..eaa2a8f03b --- /dev/null +++ b/tests/Feature/Importing/Api/ImportDataTestCase.php @@ -0,0 +1,14 @@ +postJson(route('api.imports.importFile', $parameters), $parameters); + } +} diff --git a/tests/Feature/Importing/Api/ImportLicenseTest.php b/tests/Feature/Importing/Api/ImportLicenseTest.php new file mode 100644 index 0000000000..0cd5fe447c --- /dev/null +++ b/tests/Feature/Importing/Api/ImportLicenseTest.php @@ -0,0 +1,356 @@ +actingAsForApi(User::factory()->create()); + + $this->importFileResponse(['import' => 44])->assertForbidden(); + } + + #[Test] + public function userWithImportAssetsPermissionCanImportLicenses(): void + { + $this->actingAsForApi(User::factory()->canImport()->create()); + + $import = Import::factory()->license()->create(); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function importLicenses(): void + { + $importFileBuilder = ImportFileBuilder::new(); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id]) + ->assertOk() + ->assertExactJson([ + 'payload' => null, + 'status' => 'success', + 'messages' => ['redirect_url' => route('licenses.index')] + ]); + + $newLicense = License::query() + ->withCasts(['reassignable' => 'bool']) + ->with(['category', 'company', 'manufacturer', 'supplier']) + ->where('serial', $row['serialNumber']) + ->sole(); + + $activityLogs = ActivityLog::query() + ->where('item_type', License::class) + ->where('item_id', $newLicense->id) + ->get(); + + $this->assertCount(2, $activityLogs); + + $this->assertEquals($row['licenseName'], $newLicense->name); + $this->assertEquals($row['serialNumber'], $newLicense->serial); + $this->assertEquals($row['purchaseDate'], $newLicense->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $newLicense->purchase_cost); + $this->assertEquals($row['orderNumber'], $newLicense->order_number); + $this->assertEquals($row['seats'], $newLicense->seats); + $this->assertEquals($row['notes'], $newLicense->notes); + $this->assertEquals($row['licensedToName'], $newLicense->license_name); + $this->assertEquals($row['licensedToEmail'], $newLicense->license_email); + $this->assertEquals($row['supplierName'], $newLicense->supplier->name); + $this->assertEquals($row['companyName'], $newLicense->company->name); + $this->assertEquals($row['category'], $newLicense->category->name); + $this->assertEquals($row['expirationDate'], $newLicense->expiration_date->toDateString()); + $this->assertEquals($row['isMaintained'] === 'TRUE', $newLicense->maintained); + $this->assertEquals($row['isReassignAble'] === 'TRUE', $newLicense->reassignable); + $this->assertEquals('', $newLicense->purchase_order); + $this->assertNull($newLicense->depreciation_id); + $this->assertNull($newLicense->termination_date); + $this->assertNull($newLicense->deprecate); + $this->assertNull($newLicense->min_amt); + } + + #[Test] + public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void + { + $row = ImportFileBuilder::new()->definition(); + $row['unknownColumnInCsvFile'] = 'foo'; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function willNotCreateNewLicenseWhenNameAndSerialNumberAlreadyExist(): void + { + $license = License::factory()->create(); + + $importFileBuilder = ImportFileBuilder::times(4)->replace([ + 'itemName' => $license->name, + 'serialNumber' => $license->serial + ]); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $probablyNewLicenses = License::query() + ->where('name', $license->name) + ->where('serial', $license->serial) + ->get(); + + $this->assertCount(1, $probablyNewLicenses); + } + + #[Test] + public function formatAttributes(): void + { + $importFileBuilder = ImportFileBuilder::new([ + 'expirationDate' => '2022/10/10' + ]); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newLicense = License::query() + ->where('serial', $importFileBuilder->firstRow()['serialNumber']) + ->sole(); + + $this->assertEquals('2022-10-10', $newLicense->expiration_date->toDateString()); + } + + #[Test] + public function willNotCreateNewCompanyWhenCompanyExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newLicenses = License::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['company_id']); + + $this->assertCount(1, $newLicenses->pluck('company_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewManufacturerWhenManufacturerExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['manufacturerName' => Str::random()]); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newLicenses = License::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['manufacturer_id']); + + $this->assertCount(1, $newLicenses->pluck('manufacturer_id')->unique()->all()); + } + + #[Test] + public function willNotCreateNewCategoryWhenCategoryExists(): void + { + $importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => $this->faker->company]); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newLicenses = License::query() + ->whereIn('serial', $importFileBuilder->pluck('serialNumber')) + ->get(['category_id']); + + $this->assertCount(1, $newLicenses->pluck('category_id')->unique()->all()); + } + + #[Test] + public function whenRequiredColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::times() + ->replace(['name' => '']) + ->forget(['seats']); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + $row['licenseName'] => [ + "License \"{$row['licenseName']}\"" => [ + 'seats' => ['The seats field is required.'], + ] + ] + ] + ]); + + $newLicenses = License::query() + ->where('serial', $row['serialNumber']) + ->get(); + + $this->assertCount(0, $newLicenses); + } + + #[Test] + public function updateLicenseFromImport(): void + { + $license = License::factory()->create(); + $importFileBuilder = ImportFileBuilder::new([ + 'licenseName' => $license->name, + 'serialNumber' => $license->serial + ]); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedLicense = License::query() + ->with(['manufacturer', 'category', 'supplier']) + ->where('serial', $row['serialNumber']) + ->sole(); + + $this->assertEquals($row['licenseName'], $updatedLicense->name); + $this->assertEquals($row['serialNumber'], $updatedLicense->serial); + $this->assertEquals($row['purchaseDate'], $updatedLicense->purchase_date->toDateString()); + $this->assertEquals($row['purchaseCost'], $updatedLicense->purchase_cost); + $this->assertEquals($row['orderNumber'], $updatedLicense->order_number); + $this->assertEquals($row['seats'], $updatedLicense->seats); + $this->assertEquals($row['notes'], $updatedLicense->notes); + $this->assertEquals($row['licensedToName'], $updatedLicense->license_name); + $this->assertEquals($row['licensedToEmail'], $updatedLicense->license_email); + $this->assertEquals($row['supplierName'], $updatedLicense->supplier->name); + $this->assertEquals($row['companyName'], $updatedLicense->company->name); + $this->assertEquals($row['category'], $updatedLicense->category->name); + $this->assertEquals($row['expirationDate'], $updatedLicense->expiration_date->toDateString()); + $this->assertEquals($row['isMaintained'] === 'TRUE', $updatedLicense->maintained); + $this->assertEquals($row['isReassignAble'] === 'TRUE', $updatedLicense->reassignable); + $this->assertEquals($license->purchase_order, $updatedLicense->purchase_order); + $this->assertEquals($license->depreciation_id, $updatedLicense->depreciation_id); + $this->assertEquals($license->termination_date, $updatedLicense->termination_date); + $this->assertEquals($license->deprecate, $updatedLicense->deprecate); + $this->assertEquals($license->min_amt, $updatedLicense->min_amt); + } + + #[Test] + public function customColumnMapping(): void + { + $faker = ImportFileBuilder::times()->definition(); + $row = [ + 'category' => $faker['supplierName'], + 'companyName' => $faker['serialNumber'], + 'expirationDate' => $faker['seats'], + 'isMaintained' => $faker['purchaseDate'], + 'isReassignAble' => $faker['purchaseCost'], + 'licensedToName' => $faker['orderNumber'], + 'licensedToEmail' => $faker['notes'], + 'licenseName' => $faker['licenseName'], + 'manufacturerName' => $faker['category'], + 'notes' => $faker['companyName'], + 'orderNumber' => $faker['expirationDate'], + 'purchaseCost' => $faker['isMaintained'], + 'purchaseDate' => $faker['isReassignAble'], + 'seats' => $faker['licensedToName'], + 'serialNumber' => $faker['licensedToEmail'], + 'supplierName' => $faker['manufacturerName'] + ]; + + $importFileBuilder = new ImportFileBuilder([$row]); + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse([ + 'import' => $import->id, + 'column-mappings' => [ + 'Category' => 'supplier', + 'Company' => 'serial', + 'expiration date' => 'seats', + 'maintained' => 'purchase_date', + 'reassignable' => 'purchase_cost', + 'Licensed To Name' => 'order_number', + 'Licensed To Email' => 'notes', + 'licenseName' => 'name', + 'manufacturer' => 'category', + 'Notes' => 'company', + 'Serial number' => 'license_email', + 'Order Number' => 'expiration_date', + 'purchase Cost' => 'maintained', + 'purchase Date' => 'reassignable', + 'seats' => 'license_name', + 'supplier' => 'manufacturer' + ] + ])->assertOk(); + + $newLicense = License::query() + ->with(['category', 'company', 'manufacturer', 'supplier']) + ->where('serial', $row['companyName']) + ->sole(); + + $this->assertEquals($row['licenseName'], $newLicense->name); + $this->assertEquals($row['companyName'], $newLicense->serial); + $this->assertEquals($row['isMaintained'], $newLicense->purchase_date->toDateString()); + $this->assertEquals($row['isReassignAble'], $newLicense->purchase_cost); + $this->assertEquals($row['licensedToName'], $newLicense->order_number); + $this->assertEquals($row['expirationDate'], $newLicense->seats); + $this->assertEquals($row['licensedToEmail'], $newLicense->notes); + $this->assertEquals($row['seats'], $newLicense->license_name); + $this->assertEquals($row['serialNumber'], $newLicense->license_email); + $this->assertEquals($row['category'], $newLicense->supplier->name); + $this->assertEquals($row['notes'], $newLicense->company->name); + $this->assertEquals($row['manufacturerName'], $newLicense->category->name); + $this->assertEquals($row['orderNumber'], $newLicense->expiration_date->toDateString()); + $this->assertEquals($row['purchaseCost'] === 'TRUE', $newLicense->maintained); + $this->assertEquals($row['purchaseDate'] === 'TRUE', $newLicense->reassignable); + $this->assertEquals('', $newLicense->purchase_order); + $this->assertNull($newLicense->depreciation_id); + $this->assertNull($newLicense->termination_date); + $this->assertNull($newLicense->deprecate); + $this->assertNull($newLicense->min_amt); + } +} diff --git a/tests/Feature/Importing/Api/ImportUsersTest.php b/tests/Feature/Importing/Api/ImportUsersTest.php new file mode 100644 index 0000000000..f8c0b7c743 --- /dev/null +++ b/tests/Feature/Importing/Api/ImportUsersTest.php @@ -0,0 +1,336 @@ +actingAsForApi(User::factory()->create()); + + $this->importFileResponse(['import' => 44])->assertForbidden(); + } + + #[Test] + public function userWithImportAssetsPermissionCanImportUsers(): void + { + $this->actingAsForApi(User::factory()->canImport()->create()); + + $import = Import::factory()->users()->create(); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function importUsers(): void + { + Notification::fake(); + + $importFileBuilder = ImportFileBuilder::new(); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'send-welcome' => 1]) + ->assertOk() + ->assertExactJson([ + 'payload' => null, + 'status' => 'success', + 'messages' => ['redirect_url' => route('users.index')] + ]); + + $newUser = User::query() + ->with(['company', 'location']) + ->where('username', $row['username']) + ->sole(); + + Notification::assertNothingSent(); + + $this->assertEquals($row['email'], $newUser->email); + $this->assertEquals($row['firstName'], $newUser->first_name); + $this->assertEquals($row['lastName'], $newUser->last_name); + $this->assertEquals($row['employeeNumber'], $newUser->employee_num); + $this->assertEquals($row['companyName'], $newUser->company->name); + $this->assertEquals($row['location'], $newUser->location->name); + $this->assertEquals($row['phoneNumber'], $newUser->phone); + $this->assertEquals($row['position'], $newUser->jobtitle); + $this->assertTrue(Hash::isHashed($newUser->password)); + $this->assertEquals('', $newUser->website); + $this->assertEquals('', $newUser->country); + $this->assertEquals('', $newUser->address); + $this->assertEquals('', $newUser->city); + $this->assertEquals('', $newUser->state); + $this->assertEquals('', $newUser->zip); + $this->assertNull($newUser->permissions); + $this->assertNull($newUser->avatar); + $this->assertNull($newUser->notes); + $this->assertNull($newUser->skin); + $this->assertNull($newUser->department_id); + $this->assertNull($newUser->two_factor_secret); + $this->assertNull($newUser->idap_import); + $this->assertEquals('en-US', $newUser->locale); + $this->assertEquals(1, $newUser->show_in_list); + $this->assertEquals(0, $newUser->two_factor_enrolled); + $this->assertEquals(0, $newUser->two_factor_optin); + $this->assertEquals(0, $newUser->remote); + $this->assertEquals(0, $newUser->autoassign_licenses); + $this->assertEquals(0, $newUser->vip); + $this->assertEquals(0, $newUser->enable_sounds); + $this->assertEquals(0, $newUser->enable_confetti); + $this->assertNull($newUser->created_by); + $this->assertNull($newUser->start_date); + $this->assertNull($newUser->end_date); + $this->assertNull($newUser->scim_externalid); + $this->assertNull($newUser->manager_id); + $this->assertNull($newUser->activation_code); + $this->assertNull($newUser->last_login); + $this->assertNull($newUser->persist_code); + $this->assertNull($newUser->reset_password_code); + $this->assertEquals(0, $newUser->activated); + } + + #[Test] + public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void + { + $row = ImportFileBuilder::new()->definition(); + $row['unknownColumnInCsvFile'] = 'foo'; + + $importFileBuilder = new ImportFileBuilder([$row]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->importFileResponse(['import' => $import->id])->assertOk(); + } + + #[Test] + public function willNotCreateNewUserWhenUserWithUserNameAlreadyExist(): void + { + $user = User::factory()->create(['username' => Str::random()]); + $importFileBuilder = ImportFileBuilder::times(4)->replace(['username' => $user->username]); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $probablyNewUsers = User::query() + ->where('username', $user->username) + ->get(); + + $this->assertCount(1, $probablyNewUsers); + } + + #[Test] + public function willGenerateUsernameWhenUsernameFieldIsMissing(): void + { + $importFileBuilder = ImportFileBuilder::new()->forget('username'); + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newUser = User::query() + ->where('email', $row['email']) + ->sole(); + + $generatedUsername = User::generateFormattedNameFromFullName("{$row['firstName']} {$row['lastName']}")['username']; + + $this->assertEquals($generatedUsername, $newUser->username); + } + + #[Test] + public function willUpdateLocationOfAllAssetsAssignedToUser(): void + { + $user = User::factory()->create(['username' => Str::random()]); + $assetsAssignedToUser = Asset::factory()->create(['assigned_to' => $user->id, 'assigned_type' => User::class]); + $importFileBuilder = ImportFileBuilder::new(['username' => $user->username]); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $userLocation = Location::query()->where('name', $importFileBuilder->firstRow()['location'])->sole(['id']); + + $this->assertEquals( + $userLocation->id, + $assetsAssignedToUser->refresh()->location_id + ); + } + + #[Test] + public function whenRequiredColumnsAreMissingInImportFile(): void + { + $importFileBuilder = ImportFileBuilder::new(['firstName' => ''])->forget(['username']); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse(['import' => $import->id]) + ->assertInternalServerError() + ->assertExactJson([ + 'status' => 'import-errors', + 'payload' => null, + 'messages' => [ + '' => [ + 'User' => [ + 'first_name' => ['The first name field is required.'], + ] + ] + ] + ]); + + $newUsers = User::query() + ->where('email', $importFileBuilder->firstRow()['email']) + ->get(); + + $this->assertCount(0, $newUsers); + } + + #[Test] + public function updateUserFromImport(): void + { + $user = User::factory()->create(['username' => Str::random()])->refresh(); + $importFileBuilder = ImportFileBuilder::new(['username' => $user->username]); + + $row = $importFileBuilder->firstRow(); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk(); + + $updatedUser = User::query()->with(['company', 'location'])->find($user->id); + $updatedAttributes = [ + 'first_name', 'email', 'last_name', 'employee_num', 'company', + 'location_id', 'company_id', 'updated_at', 'phone', 'jobtitle' + ]; + + $this->assertEquals($row['email'], $updatedUser->email); + $this->assertEquals($row['firstName'], $updatedUser->first_name); + $this->assertEquals($row['lastName'], $updatedUser->last_name); + $this->assertEquals($row['employeeNumber'], $updatedUser->employee_num); + $this->assertEquals($row['companyName'], $updatedUser->company->name); + $this->assertEquals($row['location'], $updatedUser->location->name); + $this->assertEquals($row['phoneNumber'], $updatedUser->phone); + $this->assertEquals($row['position'], $updatedUser->jobtitle); + $this->assertTrue(Hash::isHashed($updatedUser->password)); + + $this->assertEquals( + Arr::except($user->attributesToArray(), $updatedAttributes), + Arr::except($updatedUser->attributesToArray(), $updatedAttributes), + ); + } + + #[Test] + public function customColumnMapping(): void + { + $faker = ImportFileBuilder::new()->definition(); + $row = [ + 'companyName' => $faker['username'], + 'email' => $faker['position'], + 'employeeNumber' => $faker['phoneNumber'], + 'firstName' => $faker['location'], + 'lastName' => $faker['lastName'], + 'location' => $faker['firstName'], + 'phoneNumber' => $faker['employeeNumber'], + 'position' => $faker['email'], + 'username' => $faker['companyName'], + ]; + + $importFileBuilder = new ImportFileBuilder([$row]); + $import = Import::factory()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + + $this->importFileResponse([ + 'import' => $import->id, + 'column-mappings' => [ + 'Company' => 'username', + 'email' => 'jobtitle', + 'Employee Number' => 'phone_number', + 'First Name' => 'location', + 'Last Name' => 'last_name', + 'Location' => 'first_name', + 'Phone Number' => 'employee_num', + 'Job Title' => 'email', + 'Username' => 'company', + ] + ])->assertOk(); + + $newUser = User::query() + ->with(['company', 'location']) + ->where('username', $row['companyName']) + ->sole(); + + $this->assertEquals($row['position'], $newUser->email); + $this->assertEquals($row['location'], $newUser->first_name); + $this->assertEquals($row['lastName'], $newUser->last_name); + $this->assertEquals($row['email'], $newUser->jobtitle); + $this->assertEquals($row['phoneNumber'], $newUser->employee_num); + $this->assertEquals($row['username'], $newUser->company->name); + $this->assertEquals($row['firstName'], $newUser->location->name); + $this->assertEquals($row['employeeNumber'], $newUser->phone); + $this->assertTrue(Hash::isHashed($newUser->password)); + $this->assertEquals('', $newUser->website); + $this->assertEquals('', $newUser->country); + $this->assertEquals('', $newUser->address); + $this->assertEquals('', $newUser->city); + $this->assertEquals('', $newUser->state); + $this->assertEquals('', $newUser->zip); + $this->assertNull($newUser->permissions); + $this->assertNull($newUser->avatar); + $this->assertNull($newUser->notes); + $this->assertNull($newUser->skin); + $this->assertNull($newUser->department_id); + $this->assertNull($newUser->two_factor_secret); + $this->assertNull($newUser->idap_import); + $this->assertEquals('en-US', $newUser->locale); + $this->assertEquals(1, $newUser->show_in_list); + $this->assertEquals(0, $newUser->two_factor_enrolled); + $this->assertEquals(0, $newUser->two_factor_optin); + $this->assertEquals(0, $newUser->remote); + $this->assertEquals(0, $newUser->autoassign_licenses); + $this->assertEquals(0, $newUser->vip); + $this->assertEquals(0, $newUser->enable_sounds); + $this->assertEquals(0, $newUser->enable_confetti); + $this->assertNull($newUser->created_by); + $this->assertNull($newUser->start_date); + $this->assertNull($newUser->end_date); + $this->assertNull($newUser->scim_externalid); + $this->assertNull($newUser->manager_id); + $this->assertNull($newUser->activation_code); + $this->assertNull($newUser->last_login); + $this->assertNull($newUser->persist_code); + $this->assertNull($newUser->reset_password_code); + $this->assertEquals(0, $newUser->activated); + } +} diff --git a/tests/Feature/Licenses/Api/DeleteLicensesTest.php b/tests/Feature/Licenses/Api/DeleteLicensesTest.php new file mode 100644 index 0000000000..7db8e588d3 --- /dev/null +++ b/tests/Feature/Licenses/Api/DeleteLicensesTest.php @@ -0,0 +1,90 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.licenses.destroy', $license)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($license); + } + + public function testAdheresToFullMultipleCompaniesSupportScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $licenseA = License::factory()->for($companyA)->create(); + $licenseB = License::factory()->for($companyB)->create(); + $licenseC = License::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->deleteLicenses()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->deleteLicenses()->make()); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($userInCompanyA) + ->deleteJson(route('api.licenses.destroy', $licenseB)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($userInCompanyB) + ->deleteJson(route('api.licenses.destroy', $licenseA)) + ->assertStatusMessageIs('error'); + + $this->actingAsForApi($superUser) + ->deleteJson(route('api.licenses.destroy', $licenseC)) + ->assertStatusMessageIs('success'); + + $this->assertNotSoftDeleted($licenseA); + $this->assertNotSoftDeleted($licenseB); + $this->assertSoftDeleted($licenseC); + } + + public function testLicenseCannotBeDeletedIfStillAssigned() + { + $license = License::factory()->create(['seats' => 2]); + $license->freeSeat()->update(['assigned_to' => User::factory()->create()->id]); + + $this->actingAsForApi(User::factory()->deleteLicenses()->create()) + ->deleteJson(route('api.licenses.destroy', $license)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($license); + } + + public function testCanDeleteLicense() + { + $license = License::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteLicenses()->create()) + ->deleteJson(route('api.licenses.destroy', $license)) + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($license); + } + + public function testLicenseSeatsAreDeletedWhenLicenseIsDeleted() + { + $license = License::factory()->create(['seats' => 2]); + + $this->assertTrue($license->fresh()->licenseseats->isNotEmpty(), 'License seats not created like expected'); + + $this->actingAsForApi(User::factory()->deleteLicenses()->create()) + ->deleteJson(route('api.licenses.destroy', $license)); + + $this->assertTrue($license->fresh()->licenseseats->isEmpty()); + } +} diff --git a/tests/Feature/Locations/Api/DeleteLocationsTest.php b/tests/Feature/Locations/Api/DeleteLocationsTest.php index 270582c901..796b9a1977 100644 --- a/tests/Feature/Locations/Api/DeleteLocationsTest.php +++ b/tests/Feature/Locations/Api/DeleteLocationsTest.php @@ -5,10 +5,21 @@ namespace Tests\Feature\Locations\Api; use App\Models\Asset; use App\Models\Location; use App\Models\User; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class DeleteLocationsTest extends TestCase +class DeleteLocationsTest extends TestCase implements TestsPermissionsRequirement { + public function testRequiresPermission() + { + $location = Location::factory()->create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.locations.destroy', $location)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($location); + } public function testErrorReturnedViaApiIfLocationDoesNotExist() { @@ -90,4 +101,15 @@ class DeleteLocationsTest extends TestCase ->json(); } + public function testCanDeleteLocation() + { + $location = Location::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteLocations()->create()) + ->deleteJson(route('api.locations.destroy', $location->id)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($location); + } } diff --git a/tests/Feature/Manufacturers/Api/DeleteManufacturersTest.php b/tests/Feature/Manufacturers/Api/DeleteManufacturersTest.php new file mode 100644 index 0000000000..a5fbb5ee0a --- /dev/null +++ b/tests/Feature/Manufacturers/Api/DeleteManufacturersTest.php @@ -0,0 +1,64 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.manufacturers.destroy', $manufacturer)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($manufacturer); + } + + public function testCannotDeleteManufacturerWithAssociatedData() + { + $manufacturerWithAccessories = Manufacturer::factory()->hasAccessories()->create(); + $manufacturerWithConsumables = Manufacturer::factory()->hasConsumables()->create(); + $manufacturerWithLicenses = Manufacturer::factory()->hasLicenses()->create(); + + $manufacturerWithAssets = Manufacturer::factory()->hasAssets()->create(); + $model = AssetModel::factory()->create(['manufacturer_id' => $manufacturerWithAssets->id]); + Asset::factory()->create(['model_id' => $model->id]); + + $this->assertGreaterThan(0, $manufacturerWithAccessories->accessories->count(), 'Precondition failed: Manufacturer has no accessories'); + $this->assertGreaterThan(0, $manufacturerWithAssets->assets->count(), 'Precondition failed: Manufacturer has no assets'); + $this->assertGreaterThan(0, $manufacturerWithConsumables->consumables->count(), 'Precondition failed: Manufacturer has no consumables'); + $this->assertGreaterThan(0, $manufacturerWithLicenses->licenses->count(), 'Precondition failed: Manufacturer has no licenses'); + + $actor = $this->actingAsForApi(User::factory()->deleteManufacturers()->create()); + + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithAccessories))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithAssets))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithConsumables))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.manufacturers.destroy', $manufacturerWithLicenses))->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($manufacturerWithAssets); + $this->assertNotSoftDeleted($manufacturerWithAccessories); + $this->assertNotSoftDeleted($manufacturerWithConsumables); + $this->assertNotSoftDeleted($manufacturerWithLicenses); + } + + public function testCanDeleteManufacturer() + { + $manufacturer = Manufacturer::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteManufacturers()->create()) + ->deleteJson(route('api.manufacturers.destroy', $manufacturer)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($manufacturer); + } +} diff --git a/tests/Feature/PredefinedKits/Api/DeletePredefinedKitsTest.php b/tests/Feature/PredefinedKits/Api/DeletePredefinedKitsTest.php new file mode 100644 index 0000000000..b9ff6164a7 --- /dev/null +++ b/tests/Feature/PredefinedKits/Api/DeletePredefinedKitsTest.php @@ -0,0 +1,59 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.kits.destroy', $predefinedKit)) + ->assertForbidden(); + + $this->assertDatabaseHas('kits', ['id' => $predefinedKit->id]); + } + + public function testCanDeletePredefinedKits() + { + $predefinedKit = PredefinedKit::factory()->create(); + + $this->actingAsForApi(User::factory()->deletePredefinedKits()->create()) + ->deleteJson(route('api.kits.destroy', $predefinedKit)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertDatabaseMissing('kits', ['id' => $predefinedKit->id]); + } + + public function testAssociatedDataDetachedWhenPredefinedKitDeleted() + { + $predefinedKit = PredefinedKit::factory() + ->hasAccessories() + ->hasConsumables() + ->hasLicenses() + ->hasModels() + ->create(); + + $this->assertGreaterThan(0, $predefinedKit->accessories->count(), 'Precondition failed: PredefinedKit has no accessories'); + $this->assertGreaterThan(0, $predefinedKit->consumables->count(), 'Precondition failed: PredefinedKit has no consumables'); + $this->assertGreaterThan(0, $predefinedKit->licenses->count(), 'Precondition failed: PredefinedKit has no licenses'); + $this->assertGreaterThan(0, $predefinedKit->models->count(), 'Precondition failed: PredefinedKit has no models'); + + $this->actingAsForApi(User::factory()->deletePredefinedKits()->create()) + ->deleteJson(route('api.kits.destroy', $predefinedKit)) + ->assertStatusMessageIs('success'); + + $this->assertEquals(0, DB::table('kits_accessories')->where('kit_id', $predefinedKit->id)->count()); + $this->assertEquals(0, DB::table('kits_consumables')->where('kit_id', $predefinedKit->id)->count()); + $this->assertEquals(0, DB::table('kits_licenses')->where('kit_id', $predefinedKit->id)->count()); + $this->assertEquals(0, DB::table('kits_models')->where('kit_id', $predefinedKit->id)->count()); + } +} diff --git a/tests/Feature/Settings/AlertsSettingTest.php b/tests/Feature/Settings/AlertsSettingTest.php index 87e7c0286a..d79bd1cf21 100644 --- a/tests/Feature/Settings/AlertsSettingTest.php +++ b/tests/Feature/Settings/AlertsSettingTest.php @@ -2,22 +2,28 @@ namespace Tests\Feature\Settings; -use App\Models\Asset; use Tests\TestCase; -use Illuminate\Http\UploadedFile; -use Illuminate\Support\Facades\Storage; use App\Models\User; -use App\Models\Setting; class AlertsSettingTest extends TestCase { public function testPermissionRequiredToViewAlertSettings() { - $asset = Asset::factory()->create(); $this->actingAs(User::factory()->create()) ->get(route('settings.alerts.index')) ->assertForbidden(); } + public function testAdminCCEmailArrayCanBeSaved() + { + $response = $this->actingAs(User::factory()->superuser()->create()) + ->post(route('settings.alerts.save', ['alert_email' => 'me@example.com,you@example.com'])) + ->assertStatus(302) + ->assertValid('alert_email') + ->assertRedirect(route('settings.index')) + ->assertSessionHasNoErrors(); + $this->followRedirects($response)->assertSee('alert-success'); + } + } diff --git a/tests/Feature/Settings/BrandingSettingsTest.php b/tests/Feature/Settings/BrandingSettingsTest.php index 03e2b013f6..d751a1ab47 100644 --- a/tests/Feature/Settings/BrandingSettingsTest.php +++ b/tests/Feature/Settings/BrandingSettingsTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature\Settings; -use App\Models\Asset; use Tests\TestCase; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; diff --git a/tests/Feature/Settings/LabelSettingTest.php b/tests/Feature/Settings/LabelSettingTest.php new file mode 100644 index 0000000000..e96c684a89 --- /dev/null +++ b/tests/Feature/Settings/LabelSettingTest.php @@ -0,0 +1,18 @@ +actingAs(User::factory()->create()) + ->get(route('settings.labels.index')) + ->assertForbidden(); + } + +} diff --git a/tests/Feature/Settings/LdapSettingsTest.php b/tests/Feature/Settings/LdapSettingsTest.php new file mode 100644 index 0000000000..317ccb42df --- /dev/null +++ b/tests/Feature/Settings/LdapSettingsTest.php @@ -0,0 +1,62 @@ +actingAs(User::factory()->create()) + ->get(route('settings.ldap.index')) + ->assertForbidden(); + } + + public function testLdapSettingsCanBeSaved() + { + $response = $this->actingAs(User::factory()->superuser()->create()) + ->post(route('settings.ldap.save', [ + 'ldap_enabled' => 1, + 'ldap_username_field' => 'samaccountname', + 'ldap_filter' => 'uid=', + 'ldap_auth_filter_query' => 'uid=', + 'ldap_uname' => 'SomeUserField', + 'ldap_pword' => 'MyAwesomePassword', + 'ldap_basedn' => 'uid=', + 'ldap_fname_field' => 'SomeFirstnameField', + 'ldap_server' => 'ldaps://ldap.example.com', + ])) + ->assertStatus(302) + ->assertValid('ldap_enabled') + ->assertRedirect(route('settings.ldap.index')) + ->assertSessionHasNoErrors(); + $this->followRedirects($response)->assertSee('alert-success'); + } + + public function testLdapSettingsAreValidatedCorrectly() + { + $response = $this->actingAs(User::factory()->superuser()->create()) + ->from(route('settings.ldap.index')) + ->post(route('settings.ldap.save', [ + 'ldap_enabled' => 1, + 'ldap_username_field' => 'sAMAccountName', + 'ldap_filter' => '(uid=)', + ])) + ->assertStatus(302) + ->assertRedirect(route('settings.ldap.index')) + ->assertSessionHasErrors([ + 'ldap_username_field', + 'ldap_auth_filter_query', + 'ldap_uname', + 'ldap_pword', + 'ldap_basedn', + 'ldap_fname_field', + 'ldap_server', + ]); + $this->followRedirects($response)->assertSee('alert-danger'); + } + +} diff --git a/tests/Feature/Settings/SecuritySettingTest.php b/tests/Feature/Settings/SecuritySettingTest.php new file mode 100644 index 0000000000..6edeee673b --- /dev/null +++ b/tests/Feature/Settings/SecuritySettingTest.php @@ -0,0 +1,18 @@ +actingAs(User::factory()->create()) + ->get(route('settings.security.index')) + ->assertForbidden(); + } + +} diff --git a/tests/Feature/StatusLabels/Api/DeleteStatusLabelsTest.php b/tests/Feature/StatusLabels/Api/DeleteStatusLabelsTest.php new file mode 100644 index 0000000000..04728c7c41 --- /dev/null +++ b/tests/Feature/StatusLabels/Api/DeleteStatusLabelsTest.php @@ -0,0 +1,47 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.statuslabels.destroy', $statusLabel)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($statusLabel); + } + + public function testCannotDeleteStatusLabelWhileStillAssociatedToAssets() + { + $statusLabel = Statuslabel::factory()->hasAssets()->create(); + + $this->assertGreaterThan(0, $statusLabel->assets->count(), 'Precondition failed: StatusLabel has no assets'); + + $this->actingAsForApi(User::factory()->deleteStatusLabels()->create()) + ->deleteJson(route('api.statuslabels.destroy', $statusLabel)) + ->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($statusLabel); + } + + public function testCanDeleteStatusLabel() + { + $statusLabel = Statuslabel::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteStatusLabels()->create()) + ->deleteJson(route('api.statuslabels.destroy', $statusLabel)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($statusLabel); + } +} diff --git a/tests/Feature/Suppliers/Api/DeleteSuppliersTest.php b/tests/Feature/Suppliers/Api/DeleteSuppliersTest.php new file mode 100644 index 0000000000..7da8197bde --- /dev/null +++ b/tests/Feature/Suppliers/Api/DeleteSuppliersTest.php @@ -0,0 +1,52 @@ +create(); + + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.suppliers.destroy', $supplier)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($supplier); + } + + public function testCannotDeleteSupplierWithDataStillAssociated() + { + $supplierWithAsset = Supplier::factory()->hasAssets()->create(); + $supplierWithAssetMaintenance = Supplier::factory()->has(AssetMaintenance::factory(), 'asset_maintenances')->create(); + $supplierWithLicense = Supplier::factory()->hasLicenses()->create(); + + $actor = $this->actingAsForApi(User::factory()->deleteSuppliers()->create()); + + $actor->deleteJson(route('api.suppliers.destroy', $supplierWithAsset))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.suppliers.destroy', $supplierWithAssetMaintenance))->assertStatusMessageIs('error'); + $actor->deleteJson(route('api.suppliers.destroy', $supplierWithLicense))->assertStatusMessageIs('error'); + + $this->assertNotSoftDeleted($supplierWithAsset); + $this->assertNotSoftDeleted($supplierWithAssetMaintenance); + $this->assertNotSoftDeleted($supplierWithLicense); + } + + public function testCanDeleteSupplier() + { + $supplier = Supplier::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteSuppliers()->create()) + ->deleteJson(route('api.suppliers.destroy', $supplier)) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->assertSoftDeleted($supplier); + } +} diff --git a/tests/Feature/Users/Api/DeleteUserTest.php b/tests/Feature/Users/Api/DeleteUsersTest.php similarity index 85% rename from tests/Feature/Users/Api/DeleteUserTest.php rename to tests/Feature/Users/Api/DeleteUsersTest.php index 49625daac3..9677e5f7d4 100644 --- a/tests/Feature/Users/Api/DeleteUserTest.php +++ b/tests/Feature/Users/Api/DeleteUsersTest.php @@ -6,11 +6,22 @@ use App\Models\Company; use App\Models\LicenseSeat; use App\Models\Location; use App\Models\User; +use Tests\Concerns\TestsFullMultipleCompaniesSupport; +use Tests\Concerns\TestsPermissionsRequirement; use Tests\TestCase; -class DeleteUserTest extends TestCase +class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement { + public function testRequiresPermission() + { + $user = User::factory()->create(); + $this->actingAsForApi(User::factory()->create()) + ->deleteJson(route('api.users.destroy', $user)) + ->assertForbidden(); + + $this->assertNotSoftDeleted($user); + } public function testErrorReturnedViaApiIfUserDoesNotExist() { @@ -33,7 +44,6 @@ class DeleteUserTest extends TestCase ->json(); } - public function testDisallowUserDeletionViaApiIfStillManagingPeople() { $manager = User::factory()->create(); @@ -78,26 +88,19 @@ class DeleteUserTest extends TestCase ->json(); } - public function testDeniedPermissionsForDeletingUserViaApi() + public function testUsersCannotDeleteThemselves() { - $this->actingAsForApi(User::factory()->create()) - ->deleteJson(route('api.users.destroy', User::factory()->create())) - ->assertStatus(403) - ->json(); - } - - public function testSuccessPermissionsForDeletingUserViaApi() - { - $this->actingAsForApi(User::factory()->deleteUsers()->create()) - ->deleteJson(route('api.users.destroy', User::factory()->create())) + $user = User::factory()->deleteUsers()->create(); + $this->actingAsForApi($user) + ->deleteJson(route('api.users.destroy', $user)) ->assertOk() ->assertStatus(200) - ->assertStatusMessageIs('success') + ->assertStatusMessageIs('error') ->json(); + } - - public function testPermissionsForDeletingIfNotInSameCompanyAndNotSuperadmin() + public function testAdheresToFullMultipleCompaniesSupportScoping() { $this->settings->enableMultipleFullCompanySupport(); @@ -136,20 +139,17 @@ class DeleteUserTest extends TestCase $userFromA->refresh(); $this->assertNotNull($userFromA->deleted_at); - } - public function testUsersCannotDeleteThemselves() + public function testCanDeleteUser() { - $user = User::factory()->deleteUsers()->create(); - $this->actingAsForApi($user) + $user = User::factory()->create(); + + $this->actingAsForApi(User::factory()->deleteUsers()->create()) ->deleteJson(route('api.users.destroy', $user)) ->assertOk() - ->assertStatus(200) - ->assertStatusMessageIs('error') - ->json(); + ->assertStatusMessageIs('success'); + $this->assertSoftDeleted($user); } - - } diff --git a/tests/Support/CustomTestMacros.php b/tests/Support/CustomTestMacros.php index 993f449021..9c0caaf959 100644 --- a/tests/Support/CustomTestMacros.php +++ b/tests/Support/CustomTestMacros.php @@ -100,5 +100,26 @@ trait CustomTestMacros return $this; } ); + + TestResponse::macro( + 'assertMessagesContains', + function (array|string $keys) { + Assert::assertArrayHasKey('messages', $this, 'Response did not contain any messages'); + + if (is_string($keys)) { + $keys = [$keys]; + } + + foreach ($keys as $key) { + Assert::assertArrayHasKey( + $key, + $this['messages'], + "Response messages did not contain the key: {$key}" + ); + } + + return $this; + } + ); } } diff --git a/tests/Support/Importing/AccessoriesImportFileBuilder.php b/tests/Support/Importing/AccessoriesImportFileBuilder.php new file mode 100644 index 0000000000..488c91fdc4 --- /dev/null +++ b/tests/Support/Importing/AccessoriesImportFileBuilder.php @@ -0,0 +1,74 @@ + + */ +class AccessoriesImportFileBuilder extends FileBuilder +{ + /** + * @inheritdoc + */ + protected function getDictionary(): array + { + return [ + 'category' => 'Category', + 'companyName' => 'Company', + 'itemName' => 'Item Name', + 'location' => 'Location', + 'manufacturerName' => 'Manufacturer', + 'modelNumber' => 'Model Number', + 'notes' => 'Notes', + 'orderNumber' => 'Order Number', + 'purchaseCost' => 'Purchase Cost', + 'purchaseDate' => 'Purchase Date', + 'quantity' => 'Quantity', + 'supplierName' => 'Supplier', + ]; + } + + /** + * @inheritdoc + */ + public function definition(): array + { + $faker = fake(); + + return [ + 'category' => Str::random(), + 'companyName' => Str::random(), + 'itemName' => Str::random(), + 'location' => "{$faker->city}, {$faker->country}", + 'manufacturerName' => $faker->company, + 'modelNumber' => Str::random(), + 'notes' => $faker->sentence, + 'orderNumber' => Str::random(), + 'purchaseDate' => $faker->date(), + 'purchaseCost' => rand(1, 100), + 'quantity' => rand(1, 100), + 'supplierName' => $faker->company, + ]; + } +} diff --git a/tests/Support/Importing/AssetsImportFileBuilder.php b/tests/Support/Importing/AssetsImportFileBuilder.php new file mode 100644 index 0000000000..df573536c5 --- /dev/null +++ b/tests/Support/Importing/AssetsImportFileBuilder.php @@ -0,0 +1,92 @@ + + */ +class AssetsImportFileBuilder extends FileBuilder +{ + /** + * @inheritdoc + */ + protected function getDictionary(): array + { + return [ + 'assigneeFullName' => 'Full Name', + 'assigneeEmail' => 'Email', + 'assigneeUsername' => 'Username', + 'category' => 'Category', + 'companyName' => 'Company', + 'itemName' => 'item Name', + 'location' => 'Location', + 'manufacturerName' => 'Manufacturer', + 'model' => 'Model name', + 'modelNumber' => 'Model Number', + 'notes' => 'Notes', + 'purchaseCost' => 'Purchase Cost', + 'purchaseDate' => 'Purchase Date', + 'serialNumber' => 'Serial number', + 'supplierName' => 'Supplier', + 'status' => 'Status', + 'tag' => 'Asset Tag', + 'warrantyInMonths' => 'Warranty', + ]; + } + + /** + * @inheritdoc + */ + public function definition(): array + { + $faker = fake(); + + return [ + 'assigneeFullName' => $faker->name, + 'assigneeEmail' => $faker->email, + 'assigneeUsername' => $faker->userName, + 'category' => Str::random(), + 'companyName' => Str::random() . " {$faker->companySuffix}", + 'itemName' => Str::random(), + 'location' => "{$faker->country},{$faker->city}", + 'manufacturerName' => $faker->company, + 'model' => Str::random(), + 'modelNumber' => Str::random(), + 'notes' => $faker->sentence(5), + 'purchaseCost' => rand(1, 100_000), + 'purchaseDate' => $faker->date, + 'serialNumber' => $faker->uuid, + 'supplierName' => $faker->company, + 'status' => $faker->randomElement(['Ready to Deploy', 'Archived', 'Pending']), + 'tag' => Str::random(), + 'warrantyInMonths' => rand(1, 12), + ]; + } +} diff --git a/tests/Support/Importing/CleansUpImportFiles.php b/tests/Support/Importing/CleansUpImportFiles.php new file mode 100644 index 0000000000..8e0cbc3108 --- /dev/null +++ b/tests/Support/Importing/CleansUpImportFiles.php @@ -0,0 +1,20 @@ +beforeApplicationDestroyed(function () use ($import) { + Storage::delete('private_uploads/imports/' . $import->file_path); + }); + }); + } +} diff --git a/tests/Support/Importing/ComponentsImportFileBuilder.php b/tests/Support/Importing/ComponentsImportFileBuilder.php new file mode 100644 index 0000000000..6d0b163419 --- /dev/null +++ b/tests/Support/Importing/ComponentsImportFileBuilder.php @@ -0,0 +1,65 @@ + + */ +class ComponentsImportFileBuilder extends FileBuilder +{ + /** + * @inheritdoc + */ + protected function getDictionary(): array + { + return [ + 'category' => 'Category', + 'companyName' => 'Company', + 'itemName' => 'item Name', + 'location' => 'Location', + 'orderNumber' => 'Order Number', + 'purchaseCost' => 'Purchase Cost', + 'purchaseDate' => 'Purchase Date', + 'quantity' => 'Quantity', + 'serialNumber' => 'Serial number', + ]; + } + + /** + * @inheritdoc + */ + public function definition(): array + { + $faker = fake(); + + return [ + 'category' => Str::random(), + 'companyName' => Str::random() . " {$faker->companySuffix}", + 'itemName' => Str::random(), + 'location' => "{$faker->city}, {$faker->country}", + 'orderNumber' => "ON:COM:{$faker->uuid}", + 'purchaseCost' => rand(1, 100_000), + 'purchaseDate' => $faker->date, + 'quantity' => rand(1, 100_000), + 'serialNumber' => 'SN:COM:' . Str::random(), + ]; + } +} diff --git a/tests/Support/Importing/ConsumablesImportFileBuilder.php b/tests/Support/Importing/ConsumablesImportFileBuilder.php new file mode 100644 index 0000000000..16722a2f05 --- /dev/null +++ b/tests/Support/Importing/ConsumablesImportFileBuilder.php @@ -0,0 +1,65 @@ + + */ +class ConsumablesImportFileBuilder extends FileBuilder +{ + /** + * @inheritdoc + */ + protected function getDictionary(): array + { + return [ + 'category' => 'Category', + 'companyName' => 'Company', + 'itemName' => 'item Name', + 'location' => 'Location', + 'orderNumber' => 'Order Number', + 'purchaseCost' => 'Purchase Cost', + 'purchaseDate' => 'Purchase Date', + 'quantity' => 'Quantity', + 'supplier' => 'Supplier', + ]; + } + + /** + * @inheritdoc + */ + public function definition(): array + { + $faker = fake(); + + return [ + 'category' => Str::random(), + 'companyName' => Str::random() . " {$faker->companySuffix}", + 'itemName' => Str::random(), + 'location' => "{$faker->city}, {$faker->country}", + 'orderNumber' => "ON:CON:{$faker->uuid}", + 'purchaseCost' => rand(1, 100_000), + 'purchaseDate' => $faker->date, + 'quantity' => rand(1, 100_000), + 'supplier' => Str::random() . " {$faker->companySuffix}", + ]; + } +} diff --git a/tests/Support/Importing/FileBuilder.php b/tests/Support/Importing/FileBuilder.php new file mode 100644 index 0000000000..fad40054b4 --- /dev/null +++ b/tests/Support/Importing/FileBuilder.php @@ -0,0 +1,249 @@ + + */ + protected Collection $rows; + + /** + * Define the builders default row. + * + * @return Row + */ + abstract public function definition(); + + /** + * @param array $rows + */ + public function __construct(array $rows = []) + { + $this->rows = new Collection($rows); + } + + /** + * Get a new file builder instance. + * + * @param Row $attributes + * + * @return static + */ + public static function new(array $attributes = []) + { + $instance = new static; + + return $instance->push($instance->definition())->replace($attributes); + } + + /** + * Get a new file builder instance from an import file. + * + * @return static + */ + public static function fromFile(string $filepath) + { + $instance = new static; + + $reader = Reader::createFromPath($filepath); + $importFileHeaders = $reader->first(); + $dictionary = array_flip($instance->getDictionary()); + + foreach ($reader->getRecords() as $key => $record) { + $row = []; + + //Skip header. + if ($key === 0) { + continue; + } + + foreach ($record as $index => $value) { + $columnNameInImportFile = $importFileHeaders[$index]; + + //Try to map the value to a dictionary or use the file's + //column if the key is not defined in the dictionary. + $row[$dictionary[$columnNameInImportFile] ?? $columnNameInImportFile] = $value; + } + + $instance->push($row); + } + + return $instance; + } + + /** + * Get a new builder instance for the given number of rows. + * + * @return static + */ + public static function times(int $amountOfRows = 1) + { + $instance = new static; + + for ($i = 1; $i <= $amountOfRows; $i++) { + $instance->push($instance->definition()); + } + + return $instance; + } + + /** + * The the dictionary for mapping row keys to the corresponding import file headers. + * + * @return array + */ + protected function getDictionary(): array + { + return []; + } + + /** + * Add a new row. + * + * @param Row $row + * + * @return $this + */ + public function push(array $row) + { + if (!empty($row)) { + $this->rows->push($row); + } + + return $this; + } + + /** + * Pluck an array of values from the rows. + */ + public function pluck(string $key): array + { + return $this->rows->pluck($key)->all(); + } + + /** + * Replace the keys in each row with the values of the given replacement if they exist. + * + * @param array $replacement + * + * @return $this + */ + public function replace(array $replacement) + { + $this->rows = $this->rows->map(function (array $row) use ($replacement) { + foreach ($replacement as $key => $value) { + if (!array_key_exists($key, $row)) { + continue; + } + + $row[$key] = $value; + } + + return $row; + }); + + return $this; + } + + /** + * Remove the the given keys from all rows. + * + * @param string|array $keys + * + * @return $this + */ + public function forget(array|string $keys) + { + $keys = (array) $keys; + + $this->rows = $this->rows->map(function (array $row) use ($keys) { + foreach ($keys as $key) { + unset($row[$key]); + } + + return $row; + }); + + return $this; + } + + public function toCsv(): array + { + if ($this->rows->isEmpty()) { + return []; + } + + $headers = []; + $rows = $this->rows; + $dictionary = $this->getDictionary(); + + foreach (array_keys($rows->first()) as $key) { + $headers[] = $dictionary[$key] ?? $key; + } + + return $rows + ->map(fn (array $row) => array_values(array_combine($headers, $row))) + ->prepend($headers) + ->all(); + } + + /** + * Save the rows to the imports folder as a csv file. + * + * @return string The filename. + */ + public function saveToImportsDirectory(?string $filename = null): string + { + $filename ??= Str::random(40) . '.csv'; + + try { + $stream = fopen(config('app.private_uploads') . "/imports/{$filename}", 'w'); + + foreach ($this->toCsv() as $row) { + fputcsv($stream, $row); + } + + return $filename; + } finally { + if (is_resource($stream)) { + fclose($stream); + } + } + } + + /** + * Get the first row of the import file. + * + * @throws OutOfBoundsException + * + * @return Row + */ + public function firstRow(): array + { + return $this->rows->first(null, fn () => throw new OutOfBoundsException('Could not retrieve row from collection.')); + } + + /** + * Get the all the rows of the import file. + * + * @return array + */ + public function all(): array + { + return $this->rows->all(); + } +} diff --git a/tests/Support/Importing/LicensesImportFileBuilder.php b/tests/Support/Importing/LicensesImportFileBuilder.php new file mode 100644 index 0000000000..2b5e5c01ee --- /dev/null +++ b/tests/Support/Importing/LicensesImportFileBuilder.php @@ -0,0 +1,86 @@ + + */ +class LicensesImportFileBuilder extends FileBuilder +{ + /** + * @inheritdoc + */ + protected function getDictionary(): array + { + return [ + 'category' => 'Category', + 'companyName' => 'Company', + 'expirationDate' => 'expiration date', + 'isMaintained' => 'maintained', + 'isReassignAble' => 'reassignable', + 'licensedToName' => 'Licensed To Name', + 'licensedToEmail' => 'Licensed to Email', + 'licenseName' => 'Item name', + 'manufacturerName' => 'manufacturer', + 'notes' => 'notes', + 'orderNumber' => 'Order Number', + 'purchaseCost' => 'Purchase Cost', + 'purchaseDate' => 'Purchase Date', + 'seats' => 'seats', + 'serialNumber' => 'Serial number', + 'supplierName' => 'supplier', + ]; + } + + /** + * @inheritdoc + */ + public function definition(): array + { + $faker = fake(); + + return [ + 'category' => Str::random(), + 'companyName' => Str::random() . " {$faker->companySuffix}", + 'expirationDate' => $faker->date, + 'isMaintained' => $faker->randomElement(['TRUE', 'FALSE']), + 'isReassignAble' => $faker->randomElement(['TRUE', 'FALSE']), + 'licensedToName' => $faker->name, + 'licensedToEmail' => $faker->email, + 'licenseName' => $faker->company, + 'manufacturerName' => $faker->company, + 'notes' => $faker->sentence, + 'orderNumber' => "ON:LIC:{$faker->uuid}", + 'purchaseCost' => rand(1, 100_000), + 'purchaseDate' => $faker->date, + 'seats' => rand(1, 10), + 'serialNumber' => 'SN:LIC:' . Str::random(), + 'supplierName' => $faker->company, + ]; + } +} diff --git a/tests/Support/Importing/UsersImportFileBuilder.php b/tests/Support/Importing/UsersImportFileBuilder.php new file mode 100644 index 0000000000..0a00c995c2 --- /dev/null +++ b/tests/Support/Importing/UsersImportFileBuilder.php @@ -0,0 +1,65 @@ + + */ +class UsersImportFileBuilder extends FileBuilder +{ + /** + * @inheritdoc + */ + protected function getDictionary(): array + { + return [ + 'companyName' => 'Company', + 'email' => 'email', + 'employeeNumber' => 'Employee Number', + 'firstName' => 'First Name', + 'lastName' => 'Last Name', + 'location' => 'Location', + 'phoneNumber' => 'Phone Number', + 'position' => 'Job Title', + 'username' => 'Username', + ]; + } + + /** + * @inheritdoc + */ + public function definition(): array + { + $faker = fake(); + + return [ + 'companyName' => $faker->company, + 'email' => Str::random(32) . "@{$faker->freeEmailDomain}", + 'employeeNumber' => $faker->uuid, + 'firstName' => $faker->firstName, + 'lastName' => $faker->lastName, + 'location' => "{$faker->city}, {$faker->country}", + 'phoneNumber' => $faker->phoneNumber, + 'position' => $faker->jobTitle, + 'username' => Str::random(), + ]; + } +} diff --git a/tests/Unit/LdapTest.php b/tests/Unit/LdapTest.php index 65bdbe627c..f3bc521635 100644 --- a/tests/Unit/LdapTest.php +++ b/tests/Unit/LdapTest.php @@ -96,7 +96,7 @@ class LdapTest extends TestCase "count" => 1, 0 => [ 'sn' => 'Surname', - 'firstName' => 'FirstName' + 'firstname' => 'FirstName' ] ] ); diff --git a/tests/Unit/Listeners/LogListenerTest.php b/tests/Unit/Listeners/LogListenerTest.php index 011a5c51a4..dae8b08619 100644 --- a/tests/Unit/Listeners/LogListenerTest.php +++ b/tests/Unit/Listeners/LogListenerTest.php @@ -16,7 +16,7 @@ class LogListenerTest extends TestCase $checkedOutTo = User::factory()->create(); $checkedOutBy = User::factory()->create(); - // Simply to ensure `user_id` is set in the action log + // Simply to ensure `created_by` is set in the action log $this->actingAs($checkedOutBy); (new LogListener())->onCheckoutableCheckedOut(new CheckoutableCheckedOut( @@ -28,7 +28,7 @@ class LogListenerTest extends TestCase $this->assertDatabaseHas('action_logs', [ 'action_type' => 'checkout', - 'user_id' => $checkedOutBy->id, + 'created_by' => $checkedOutBy->id, 'target_id' => $checkedOutTo->id, 'target_type' => User::class, 'item_id' => $asset->id,