diff --git a/.all-contributorsrc b/.all-contributorsrc index fe7e73ccb9..ec5db50211 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3172,6 +3172,15 @@ "contributions": [ "code" ] + }, + { + "login": "arne-kroeger", + "name": "arne-kroeger", + "avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4", + "profile": "https://github.com/arne-kroeger", + "contributions": [ + "code" + ] } ] } diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 298d21b8b1..0e4efc9746 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -51,7 +51,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken | [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | | [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | | [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | -| [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | +| [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! diff --git a/app/Console/Commands/Purge.php b/app/Console/Commands/Purge.php index 7abd83c85c..1dd2aaa51d 100644 --- a/app/Console/Commands/Purge.php +++ b/app/Console/Commands/Purge.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Models\Accessory; +use App\Models\Actionlog; use App\Models\Asset; use App\Models\AssetModel; use App\Models\Category; @@ -15,6 +16,8 @@ use App\Models\Statuslabel; use App\Models\Supplier; use App\Models\User; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class Purge extends Command { @@ -141,6 +144,20 @@ class Purge extends Command $this->info($users->count().' users purged.'); $user_assoc = 0; foreach ($users as $user) { + + $rel_path = 'private_uploads/users'; + $filenames = Actionlog::where('action_type', 'uploaded') + ->where('item_id', $user->id) + ->pluck('filename'); + foreach($filenames as $filename) { + try { + if (Storage::exists($rel_path . '/' . $filename)) { + Storage::delete($rel_path . '/' . $filename); + } + } catch (\Exception $e) { + Log::info('An error occurred while deleting files: ' . $e->getMessage()); + } + } $this->info('- User "'.$user->username.'" deleted.'); $user_assoc += $user->userlog()->count(); $user->userlog()->forceDelete(); diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index 1cc9b280e1..846c2933c5 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -30,8 +30,11 @@ class SQLStreamer { public function parse_sql(string $line): string { // take into account the 'start of line or not' setting as an instance variable? // 'continuation' lines for a permitted statement are PERMITTED. + // remove *only* line-feeds & carriage-returns; helpful for regexes against lines from + // Windows dumps + $line = trim($line, "\r\n"); if($this->statement_is_permitted && $line[0] === ' ') { - return $line; + return $line . "\n"; //re-add the newline } $table_regex = '`?([a-zA-Z0-9_]+)`?'; @@ -42,8 +45,12 @@ class SQLStreamer { "/^(INSERT INTO )$table_regex(.*)$/" => false, "/^UNLOCK TABLES/" => false, // "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here? - "/^\\)[a-zA-Z0-9_= ]*;$/" => false - // ^^^^^^ that bit should *exit* the 'perimitted' black + "/^\\)[a-zA-Z0-9_= ]*;$/" => false, + // ^^^^^^ that bit should *exit* the 'permitted' block + "/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line + /* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with + a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to + assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */ ]; foreach($allowed_statements as $statement => $statechange) { @@ -67,7 +74,7 @@ class SQLStreamer { } //how do we *replace* the tablename? // print "RETURNING LINE: $line"; - return $line; + return $line . "\n"; //re-add newline } } // all that is not allowed is denied. @@ -85,7 +92,7 @@ class SQLStreamer { $parser->line_aware_piping(); // <----- THIS is doing the heavy lifting! $check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics? - //can't use 'users' because the 'accessories_users' table? + //can't use 'users' because the 'accessories_checkout' table? // can't use 'assets' because 'ver1_components_assets' foreach($check_tables as $check_table => $_ignore) { foreach ($parser->tablenames as $tablename => $_count) { @@ -164,7 +171,8 @@ class RestoreFromBackup extends Command {filename : The zip file to be migrated} {--no-progress : Don\'t show a progress bar} {--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL} - {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}'; + {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix} + {--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }'; /** * The console command description. @@ -365,6 +373,15 @@ class RestoreFromBackup extends Command return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL."); } + // If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness + if ($this->option('sql-stdout-only')) { + $sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix')); + $bytes_read = $sql_importer->line_aware_piping(); + return $this->warn("$bytes_read total bytes read"); + //TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL, + // which would be good for redirecting to a file, and not having to trim the last line off of it + } + //how to invoke the restore? $pipes = []; @@ -466,6 +483,9 @@ class RestoreFromBackup extends Command $ugly_file_name = $za->statIndex($file_details['index'])['name']; $fp = $za->getStream($ugly_file_name); //$this->info("Weird problem, here are file details? ".print_r($file_details,true)); + if (!is_dir($file_details['dest'])) { + mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that + } $migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w'); while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) { fwrite($migrated_file, $buffer); diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index d77f41b01f..18e149b57d 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -721,7 +721,7 @@ class Helper { $alert_threshold = \App\Models\Setting::getSettings()->alert_threshold; $consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get(); - $accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get(); + $accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get(); $components = Component::whereNotNull('min_amt')->get(); $asset_models = AssetModel::where('min_amt', '>', 0)->get(); $licenses = License::where('min_amt', '>', 0)->get(); @@ -749,7 +749,7 @@ class Helper } foreach ($accessories as $accessory) { - $avail = $accessory->qty - $accessory->users_count; + $avail = $accessory->qty - $accessory->checkouts_count; if ($avail < ($accessory->min_amt) + $alert_threshold) { if ($accessory->qty > 0) { $percent = number_format((($avail / $accessory->qty) * 100), 0); @@ -1484,35 +1484,57 @@ class Helper } - static public function getRedirectOption($request, $id, $table, $asset_id = null) + static public function getRedirectOption($request, $id, $table, $item_id = null) { $redirect_option = Session::get('redirect_option'); $checkout_to_type = Session::get('checkout_to_type'); - //return to index - if ($redirect_option == '0') { + // return to index + if ($redirect_option == 'index') { switch ($table) { case "Assets": - return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success')); + return route('hardware.index'); + case "Users": + return route('users.index'); + case "Licenses": + return route('licenses.index'); + case "Accessories": + return route('accessories.index'); + case "Components": + return route('components.index'); + case "Consumables": + return route('consumables.index'); } } - //return to thing being assigned - if ($redirect_option == '1') { + + // return to thing being assigned + if ($redirect_option == 'item') { switch ($table) { case "Assets": - return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success')); + return route('hardware.show', $id ?? $item_id); + case "Users": + return route('users.show', $id ?? $item_id); + case "Licenses": + return route('licenses.show', $id ?? $item_id); + case "Accessories": + return route('accessories.show', $id ?? $item_id); + case "Components": + return route('components.show', $id ?? $item_id); + case "Consumables": + return route('consumables.show', $id ?? $item_id); } } - //return to thing being assigned to - if ($redirect_option == '2') { + + // return to assignment target + if ($redirect_option == 'target') { switch ($checkout_to_type) { case 'user': - return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success')); + return route('users.show', ['user' => $request->assigned_user]); case 'location': - return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success')); + return route('locations.show', ['location' => $request->assigned_location]); case 'asset': - return redirect()->route('hardware.show', $request->assigned_asset)->with('success', trans('admin/hardware/message.checkout.success')); + return route('hardware.show', ['hardware' => $request->assigned_asset]); } } return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error')); diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index bb2e74899b..4fd5a4c547 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -79,10 +79,11 @@ class AccessoriesController extends Controller $accessory = $request->handleImages($accessory); + session()->put(['redirect_option' => $request->get('redirect_option')]); // Was the accessory created? if ($accessory->save()) { // Redirect to the new accessory page - return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success')); } return redirect()->back()->withInput()->withErrors($accessory->getErrors()); @@ -143,12 +144,12 @@ class AccessoriesController extends Controller */ public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse { - if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) { + if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) { $this->authorize($accessory); $validator = Validator::make($request->all(), [ - "qty" => "required|numeric|min:$accessory->users_count" + "qty" => "required|numeric|min:$accessory->checkouts_count" ]); if ($validator->fails()) { @@ -176,9 +177,10 @@ class AccessoriesController extends Controller $accessory = $request->handleImages($accessory); - // Was the accessory updated? + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($accessory->save()) { - return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success')); } } else { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); @@ -231,7 +233,7 @@ class AccessoriesController extends Controller */ public function show($accessoryID = null) : View | RedirectResponse { - $accessory = Accessory::withCount('users as users_count')->find($accessoryID); + $accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID); $this->authorize('view', $accessory); if (isset($accessory->id)) { return view('accessories/view', compact('accessory')); diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php index eff635d24a..e36f8a240d 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php @@ -3,8 +3,10 @@ namespace App\Http\Controllers\Accessories; use App\Events\CheckoutableCheckedIn; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Accessory; +use App\Models\AccessoryCheckout; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -23,7 +25,7 @@ class AccessoryCheckinController extends Controller */ public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse { - if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { + if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found')); } @@ -38,16 +40,16 @@ class AccessoryCheckinController extends Controller * * @uses Accessory::checkin_email() to determine if an email can and should be sent * @author [A. Gianotto] [] - * @param null $accessoryUserId + * @param null $accessoryCheckoutId * @param string $backto */ - public function store(Request $request, $accessoryUserId = null, $backto = null) : RedirectResponse + public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse { - if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { + if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); } - $accessory = Accessory::find($accessory_user->accessory_id); + $accessory = Accessory::find($accessory_checkout->accessory_id); $this->authorize('checkin', $accessory); @@ -58,12 +60,12 @@ class AccessoryCheckinController extends Controller } // Was the accessory updated? - if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { - $return_to = e($accessory_user->assigned_to); + if ($accessory_checkout->delete()) { + event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at)); - event(new CheckoutableCheckedIn($accessory, User::find($return_to), auth()->user(), $request->input('note'), $checkin_at)); + session()->put(['redirect_option' => $request->get('redirect_option')]); - return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success')); } // Redirect to the accessory management page with error return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error')); diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php index 19c8c6c7c5..03fb6ac250 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php @@ -3,9 +3,12 @@ namespace App\Http\Controllers\Accessories; use App\Events\CheckoutableCheckedOut; +use App\Helpers\Helper; +use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\Controller; use App\Http\Requests\AccessoryCheckoutRequest; use App\Models\Accessory; +use App\Models\AccessoryCheckout; use App\Models\User; use Carbon\Carbon; use Illuminate\Http\Request; @@ -15,6 +18,9 @@ use \Illuminate\Http\RedirectResponse; class AccessoryCheckoutController extends Controller { + + use CheckInOutRequest; + /** * Return the form to checkout an Accessory to a user. * @@ -24,7 +30,7 @@ class AccessoryCheckoutController extends Controller public function create($id) : View | RedirectResponse { - if ($accessory = Accessory::withCount('users as users_count')->find($id)) { + if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) { $this->authorize('checkout', $accessory); @@ -57,29 +63,38 @@ class AccessoryCheckoutController extends Controller * * @author [A. Gianotto] [] * @param Request $request - * @param int $accessory + * @param Accessory $accessory */ public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse { - + $this->authorize('checkout', $accessory); - $accessory->assigned_to = $request->input('assigned_to'); - $user = User::find($request->input('assigned_to')); - $accessory->checkout_qty = $request->input('checkout_qty', 1); + $target = $this->determineCheckoutTarget(); + + $accessory->checkout_qty = $request->input('checkout_qty', 1); + for ($i = 0; $i < $accessory->checkout_qty; $i++) { - $accessory->users()->attach($accessory->id, [ + AccessoryCheckout::create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), 'user_id' => Auth::id(), - 'assigned_to' => $request->input('assigned_to'), + 'assigned_to' => $target->id, + 'assigned_type' => $target::class, 'note' => $request->input('note'), ]); } - event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note'))); + event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note'))); + + // Set this as user since we only allow checkout to user for this item type + $request->request->add(['checkout_to_type' => request('checkout_to_type')]); + $request->request->add(['assigned_user' => $target->id]); + + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + // Redirect to the new accessory page - return redirect()->route('accessories.index') + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories')) ->with('success', trans('admin/accessories/message.checkout.success')); } } diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index 1ffdcaf193..b1506e4f40 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; use App\Events\CheckoutableCheckedOut; use App\Helpers\Helper; +use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\Controller; use App\Http\Requests\AccessoryCheckoutRequest; use App\Http\Requests\StoreAccessoryRequest; @@ -17,10 +18,12 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; - +use App\Models\AccessoryCheckout; class AccessoriesController extends Controller { + use CheckInOutRequest; + /** * Display a listing of the resource. * @@ -48,13 +51,13 @@ class AccessoriesController extends Controller 'min_amt', 'company_id', 'notes', - 'users_count', + 'checkouts_count', 'qty', ]; - $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier') - ->withCount('users as users_count'); + $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier') + ->withCount('checkouts as checkouts_count'); if ($request->filled('search')) { $accessories = $accessories->TextSearch($request->input('search')); @@ -154,7 +157,7 @@ class AccessoriesController extends Controller public function show($id) { $this->authorize('view', Accessory::class); - $accessory = Accessory::withCount('users as users_count')->findOrFail($id); + $accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id); return (new AccessoriesTransformer)->transformAccessory($accessory); } @@ -197,28 +200,23 @@ class AccessoriesController extends Controller $offset = request('offset', 0); $limit = request('limit', 50); - $accessory_users = $accessory->users; - $total = $accessory_users->count(); + $accessory_checkouts = $accessory->checkouts; + $total = $accessory_checkouts->count(); if ($total < $offset) { $offset = 0; } - $accessory_users = $accessory->users()->skip($offset)->take($limit)->get(); + $accessory_checkouts = $accessory->checkouts()->skip($offset)->take($limit)->get(); if ($request->filled('search')) { - $accessory_users = $accessory->users() - ->where(function ($query) use ($request) { - $search_str = '%' . $request->input('search') . '%'; - $query->where('first_name', 'like', $search_str) - ->orWhere('last_name', 'like', $search_str) - ->orWhere('note', 'like', $search_str); - }) + + $accessory_checkouts = $accessory->checkouts()->TextSearch($request->input('search')) ->get(); - $total = $accessory_users->count(); + $total = $accessory_checkouts->count(); } - return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total); + return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_checkouts, $total); } @@ -282,22 +280,22 @@ class AccessoriesController extends Controller public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory) { $this->authorize('checkout', $accessory); - $accessory->assigned_to = $request->input('assigned_to'); - $user = User::find($request->input('assigned_to')); + $target = $this->determineCheckoutTarget(); $accessory->checkout_qty = $request->input('checkout_qty', 1); for ($i = 0; $i < $accessory->checkout_qty; $i++) { - $accessory->users()->attach($accessory->id, [ + AccessoryCheckout::create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), 'user_id' => Auth::id(), - 'assigned_to' => $request->input('assigned_to'), + 'assigned_to' => $target->id, + 'assigned_type' => $target::class, 'note' => $request->input('note'), ]); } // Set this value to be able to pass the qty through to the event - event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note'))); + event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success'))); @@ -316,19 +314,19 @@ class AccessoriesController extends Controller */ public function checkin(Request $request, $accessoryUserId = null) { - if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { + if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) { return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); } - $accessory = Accessory::find($accessory_user->accessory_id); + $accessory = Accessory::find($accessory_checkout->accessory_id); $this->authorize('checkin', $accessory); - $logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note')); + $logaction = $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note')); // Was the accessory updated? - if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { - if (! is_null($accessory_user->assigned_to)) { - $user = User::find($accessory_user->assigned_to); + if ($accessory_checkout->delete()) { + if (! is_null($accessory_checkout->assigned_to)) { + $user = User::find($accessory_checkout->assigned_to); } $data['log_id'] = $logaction->id; diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index 579d80eeb5..71ad01b59b 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -24,7 +24,7 @@ class LicensesController extends Controller { $this->authorize('view', License::class); - $licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count'); + $licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count'); if ($request->filled('company_id')) { $licenses->where('company_id', '=', $request->input('company_id')); @@ -70,6 +70,9 @@ 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('maintained')) && ($request->input('maintained')=='true')) { $licenses->where('maintained','=',1); @@ -113,6 +116,9 @@ class LicensesController extends Controller case 'company': $licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order); break; + case 'created_by': + $licenses = $licenses->OrderCreatedBy($order); + break; default: $allowed_columns = [ diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index 2ceeb8374e..73227e0825 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -5,8 +5,10 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; use App\Http\Controllers\Controller; +use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\LocationsTransformer; use App\Http\Transformers\SelectlistTransformer; +use App\Models\Asset; use App\Models\Location; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; @@ -222,6 +224,15 @@ class LocationsController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors())); } + public function assets(Request $request, Location $location) : JsonResponse | array + { + $this->authorize('view', Asset::class); + $this->authorize('view', $location); + $assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc'); + $assets = $assets->get(); + return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request); + } + /** * Remove the specified resource from storage. * diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php index 4794fa0411..f84a468a60 100644 --- a/app/Http/Controllers/Assets/AssetCheckinController.php +++ b/app/Http/Controllers/Assets/AssetCheckinController.php @@ -11,7 +11,6 @@ use App\Models\Asset; use App\Models\CheckoutAcceptance; use App\Models\LicenseSeat; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Log; use \Illuminate\Contracts\View\View; use \Illuminate\Http\RedirectResponse; @@ -83,7 +82,6 @@ class AssetCheckinController extends Controller } $asset->expected_checkin = null; - //$asset->last_checkout = null; $asset->last_checkin = now(); $asset->assignedTo()->disassociate($asset); $asset->accepted = null; @@ -128,12 +126,12 @@ class AssetCheckinController extends Controller $acceptance->delete(); }); - Session::put('redirect_option', $request->get('redirect_option')); - // Was the asset updated? + session()->put('redirect_option', $request->get('redirect_option')); + if ($asset->save()) { event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); - return Helper::getRedirectOption($asset, $assetId, 'Assets'); + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success')); } // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors()); diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php index 355f9387bb..05b766916b 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -109,10 +109,11 @@ class AssetCheckoutController extends Controller } } - Session::put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) { - return Helper::getRedirectOption($request, $assetId, 'Assets'); + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('success', trans('admin/hardware/message.checkout.success')); } // Redirect to the asset management page with error return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors()); diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 8fecff7e12..2430cd00fa 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -204,8 +204,12 @@ class AssetsController extends Controller } } + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + + if ($success) { - return redirect()->route('hardware.index') + + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)])); @@ -289,6 +293,7 @@ class AssetsController extends Controller */ public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse { + // Check if the asset exists if (! $asset = Asset::find($assetId)) { // Redirect to the asset management page with error @@ -331,7 +336,7 @@ class AssetsController extends Controller $status = Statuslabel::find($asset->status_id); - if($status->archived){ + if ($status && $status->archived) { $asset->assigned_to = null; } @@ -350,14 +355,26 @@ class AssetsController extends Controller } // Update the asset data - $asset_tag = $request->input('asset_tags'); + $serial = $request->input('serials'); + $asset->serial = $request->input('serials'); + + if (is_array($request->input('serials'))) { + $asset->serial = $serial[1]; + } + $asset->name = $request->input('name'); - $asset->serial = $serial[1]; $asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); - $asset->asset_tag = $asset_tag[1]; + + $asset_tags = $request->input('asset_tags'); + $asset->asset_tag = $request->input('asset_tags'); + + if (is_array($request->input('asset_tags'))) { + $asset->asset_tag = $asset_tags[1]; + } + $asset->notes = $request->input('notes'); $asset = $request->handleImages($asset); @@ -369,6 +386,7 @@ class AssetsController extends Controller $model = AssetModel::find($request->get('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { + if ($field->field_encrypted == '1') { if (Gate::allows('admin')) { if (is_array($request->input($field->db_column))) { @@ -387,9 +405,10 @@ class AssetsController extends Controller } } + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); if ($asset->save()) { - return redirect()->route('hardware.show', $assetId) + return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets')) ->with('success', trans('admin/hardware/message.update.success')); } @@ -459,9 +478,16 @@ class AssetsController extends Controller $tag = $tag ? $tag : $request->get('assetTag'); $topsearch = ($request->get('topsearch') == 'true'); - if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) { - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); + // Search for an exact and unique asset tag match + $assets = Asset::where('asset_tag', '=', $tag); + + // If not a unique result, redirect to the index view + if ($assets->count() != 1) { + return redirect()->route('hardware.index') + ->with('search', $tag) + ->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ])); } + $asset = $assets->first(); $this->authorize('view', $asset); return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch); diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index fe1b331289..f1cfbc8536 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -87,7 +87,7 @@ class ResetPasswordController extends Controller 'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'), ]; - $request->validate($this->rules(), $request->all(), $this->validationErrorMessages()); + $request->validate($this->rules()); Log::debug('Checking if '.$request->input('username').' exists'); // Check to see if the user even exists - we'll treat the response the same to prevent user sniffing diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php index b59237a5d0..379882c3c5 100644 --- a/app/Http/Controllers/Components/ComponentCheckinController.php +++ b/app/Http/Controllers/Components/ComponentCheckinController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components; use App\Events\CheckoutableCheckedIn; use App\Events\ComponentCheckedIn; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Asset; use App\Models\Component; @@ -96,12 +97,10 @@ class ComponentCheckinController extends Controller $asset = Asset::find($component_assets->asset_id); event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now())); - if ($backto == 'asset'){ - return redirect()->route('hardware.show', $asset->id)->with('success', - trans('admin/components/message.checkin.success')); - } - return redirect()->route('components.index')->with('success', + session()->put(['redirect_option' => $request->get('redirect_option')]); + + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkin.success')); } diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php index fc319b47de..5547b71702 100644 --- a/app/Http/Controllers/Components/ComponentCheckoutController.php +++ b/app/Http/Controllers/Components/ComponentCheckoutController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components; use App\Events\CheckoutableCheckedOut; use App\Events\ComponentCheckedOut; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Asset; use App\Models\Component; @@ -93,14 +94,14 @@ class ComponentCheckoutController extends Controller ->withInput(); } - // Check if the user exists + // Check if the asset exists $asset = Asset::find($request->input('asset_id')); // Update the component data $component->asset_id = $request->input('asset_id'); $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'user_id' => auth()->user(), + 'user_id' => auth()->user()->id, 'created_at' => date('Y-m-d H:i:s'), 'assigned_qty' => $request->input('assigned_qty'), 'asset_id' => $request->input('asset_id'), @@ -109,6 +110,11 @@ class ComponentCheckoutController extends Controller event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note'))); - return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success')); + $request->request->add(['checkout_to_type' => 'asset']); + $request->request->add(['assigned_asset' => $asset->id]); + + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success')); } } diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php index 33ebde6456..57cd0a2b45 100644 --- a/app/Http/Controllers/Components/ComponentsController.php +++ b/app/Http/Controllers/Components/ComponentsController.php @@ -86,8 +86,10 @@ class ComponentsController extends Controller $component = $request->handleImages($component); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($component->save()) { - return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success')); } return redirect()->back()->withInput()->withErrors($component->getErrors()); @@ -160,8 +162,10 @@ class ComponentsController extends Controller $component = $request->handleImages($component); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($component->save()) { - return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success')); } return redirect()->back()->withInput()->withErrors($component->getErrors()); diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index fd690fede8..1bdb16af92 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Consumables; use App\Events\CheckoutableCheckedOut; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Consumable; use App\Models\User; @@ -33,7 +34,7 @@ class ConsumableCheckoutController extends Controller // Make sure there is at least one available to checkout if ($consumable->numRemaining() <= 0){ return redirect()->route('consumables.index') - ->with('error', trans('admin/consumables/message.checkout.unavailable')); + ->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()])); } // Return the checkout view @@ -76,7 +77,7 @@ class ConsumableCheckoutController extends Controller // Make sure there is at least one available to checkout if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) { - return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable')); + return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ])); } $admin_user = auth()->user(); @@ -101,7 +102,13 @@ class ConsumableCheckoutController extends Controller } event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); + $request->request->add(['checkout_to_type' => 'user']); + $request->request->add(['assigned_user' => $user->id]); + + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + + // Redirect to the new consumable page - return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success')); + return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success')); } } diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index 883d5849ee..42c0766fe0 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -87,8 +87,10 @@ class ConsumablesController extends Controller $consumable = $request->handleImages($consumable); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($consumable->save()) { - return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success')); } return redirect()->back()->withInput()->withErrors($consumable->getErrors()); @@ -160,8 +162,10 @@ class ConsumablesController extends Controller $consumable = $request->handleImages($consumable); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($consumable->save()) { - return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success')); } return redirect()->back()->withInput()->withErrors($consumable->getErrors()); diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index e863aa860e..dd83d0154c 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Licenses; use App\Events\CheckoutableCheckedIn; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\License; use App\Models\LicenseSeat; @@ -100,15 +101,15 @@ class LicenseCheckinController extends Controller $licenseSeat->asset_id = null; $licenseSeat->notes = $request->input('notes'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + + // Was the asset updated? if ($licenseSeat->save()) { event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes'))); - if ($backTo == 'user') { - return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success')); - } - return redirect()->route('licenses.show', $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success')); + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success')); } // Redirect to the license page with error diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 2fb0434f62..c08980fc06 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Licenses; use App\Events\CheckoutableCheckedOut; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\LicenseCheckoutRequest; use App\Models\Accessory; @@ -81,10 +82,27 @@ class LicenseCheckoutController extends Controller $checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type')); - if ($this->$checkoutMethod($licenseSeat)) { - return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success')); + + if ($request->filled('asset_id')) { + + $checkoutTarget = $this->checkoutToAsset($licenseSeat); + $request->request->add(['assigned_asset' => $checkoutTarget->id]); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']); + + } elseif ($request->filled('assigned_to')) { + $checkoutTarget = $this->checkoutToUser($licenseSeat); + $request->request->add(['assigned_user' => $checkoutTarget->id]); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']); } + + + if ($checkoutTarget) { + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success')); + } + + + return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.')); } @@ -120,8 +138,7 @@ class LicenseCheckoutController extends Controller } if ($licenseSeat->save()) { event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes'))); - - return true; + return $target; } return false; @@ -137,8 +154,7 @@ class LicenseCheckoutController extends Controller if ($licenseSeat->save()) { event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes'))); - - return true; + return $target; } return false; diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php index 01de4b4d46..7a51344dd0 100755 --- a/app/Http/Controllers/Licenses/LicensesController.php +++ b/app/Http/Controllers/Licenses/LicensesController.php @@ -102,8 +102,10 @@ class LicensesController extends Controller $license->user_id = Auth::id(); $license->min_amt = $request->input('min_amt'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($license->save()) { - return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success')); } return redirect()->back()->withInput()->withErrors($license->getErrors()); @@ -180,8 +182,10 @@ class LicensesController extends Controller $license->category_id = $request->input('category_id'); $license->min_amt = $request->input('min_amt'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($license->save()) { - return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success')); } // If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php return redirect()->back()->withInput()->withErrors($license->getErrors()); diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index d186d42cf4..9c7392560d 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -1255,7 +1255,7 @@ class SettingsController extends Controller DB::table('users')->update(['remember_token' => null]); Auth::logout(); - return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.'); + return redirect()->route('login')->with('success', trans('admin/settings/message.restore.success')); } else { return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); } diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index a3bf2ad0c6..bfcc629229 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -220,7 +220,7 @@ class BulkUsersController extends Controller $users = User::whereIn('id', $user_raw_array)->get(); $assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get(); - $accessoryUserRows = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get(); + $accessoryUserRows = DB::table('accessories_checkout')->whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get(); $licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get(); $consumableUserRows = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get(); diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 7cff29fe59..1e203e71d5 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -133,6 +133,8 @@ class UsersController extends Controller // we have to invoke the app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($user->save()) { if ($request->filled('groups')) { $user->groups()->sync($request->input('groups')); @@ -152,7 +154,7 @@ class UsersController extends Controller $user->notify(new WelcomeNotification($data)); } - return redirect()->route('users.index')->with('success', trans('admin/users/message.success.create')); + return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); @@ -309,10 +311,11 @@ class UsersController extends Controller // Handle uploaded avatar app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); + session()->put(['redirect_option' => $request->get('redirect_option')]); if ($user->save()) { // Redirect to the user page - return redirect()->route('users.index') + return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users')) ->with('success', trans('admin/users/message.success.update')); } diff --git a/app/Http/Requests/AccessoryCheckoutRequest.php b/app/Http/Requests/AccessoryCheckoutRequest.php index 0e17b390c2..deda07f8ff 100644 --- a/app/Http/Requests/AccessoryCheckoutRequest.php +++ b/app/Http/Requests/AccessoryCheckoutRequest.php @@ -44,13 +44,10 @@ class AccessoryCheckoutRequest extends ImageUploadRequest return array_merge( [ - 'assigned_to' => [ - 'required', - 'integer', - 'exists:users,id,deleted_at,NULL', - 'not_array' - ], - + 'assigned_user' => 'required_without_all:assigned_asset,assigned_location', + 'assigned_asset' => 'required_without_all:assigned_user,assigned_location', + 'assigned_location' => 'required_without_all:assigned_user,assigned_asset', + 'number_remaining_after_checkout' => [ 'min:0', 'required', diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index 007dd26843..b2feb72f79 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; +use App\Http\Requests\Traits\MayContainCustomFields; use App\Models\Asset; use App\Models\Company; use App\Models\Setting; @@ -11,6 +12,7 @@ use Illuminate\Support\Facades\Gate; class StoreAssetRequest extends ImageUploadRequest { + use MayContainCustomFields; /** * Determine if the user is authorized to make this request. * diff --git a/app/Http/Requests/Traits/MayContainCustomFields.php b/app/Http/Requests/Traits/MayContainCustomFields.php new file mode 100644 index 0000000000..50b239b60e --- /dev/null +++ b/app/Http/Requests/Traits/MayContainCustomFields.php @@ -0,0 +1,40 @@ +method() == 'POST') { + $asset_model = AssetModel::find($this->model_id); + } + if ($this->method() == 'PATCH' || $this->method() == 'PUT') { + // this is dependent on the asset update request PR + $asset_model = $this->asset; + } + // collect the custom fields in the request + $validator->after(function ($validator) use ($asset_model) { + $request_fields = $this->collect()->keys()->filter(function ($attributes) { + return str_starts_with($attributes, '_snipeit_'); + }); + // if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag + if (count($request_fields) > 0) { + $request_fields->diff($asset_model->fieldset->fields->pluck('db_column')) + ->each(function ($request_field_name) use ($request_fields, $validator) { + if (CustomField::where('db_column', $request_field_name)->exists()) { + $validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found_on_model')); + } else { + $validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found')); + } + }); + } + }); + } +} + diff --git a/app/Http/Transformers/AccessoriesTransformer.php b/app/Http/Transformers/AccessoriesTransformer.php index 7b79973a9a..c85c4e86f4 100644 --- a/app/Http/Transformers/AccessoriesTransformer.php +++ b/app/Http/Transformers/AccessoriesTransformer.php @@ -39,7 +39,7 @@ class AccessoriesTransformer '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(), - 'users_count' => $accessory->users_count, + 'checkouts_count' => $accessory->checkouts_count, 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), @@ -66,27 +66,42 @@ class AccessoriesTransformer return $array; } - public function transformCheckedoutAccessory($accessory, $accessory_users, $total) + public function transformCheckedoutAccessory($accessory, $accessory_checkouts, $total) { $array = []; - foreach ($accessory_users as $user) { + foreach ($accessory_checkouts as $checkout) { $array[] = [ - - 'assigned_pivot_id' => $user->pivot->id, - 'id' => (int) $user->id, - 'username' => e($user->username), - 'name' => e($user->getFullNameAttribute()), - 'first_name'=> e($user->first_name), - 'last_name'=> e($user->last_name), - 'employee_number' => e($user->employee_num), - 'checkout_notes' => e($user->pivot->note), - 'last_checkout' => Helper::getFormattedDateObject($user->pivot->created_at, 'datetime'), - 'type' => 'user', + 'id' => $checkout->id, + 'assigned_to' => $this->transformAssignedTo($checkout), + 'checkout_notes' => e($checkout->note), + 'last_checkout' => Helper::getFormattedDateObject($checkout->created_at, 'datetime'), 'available_actions' => ['checkin' => true], ]; } return (new DatatablesTransformer)->transformDatatables($array, $total); } + + public function transformAssignedTo($accessoryCheckout) + { + if ($accessoryCheckout->checkedOutToUser()) { + return [ + 'id' => (int) $accessoryCheckout->assigned->id, + 'username' => e($accessoryCheckout->assigned->username), + 'name' => e($accessoryCheckout->assigned->getFullNameAttribute()), + 'first_name'=> e($accessoryCheckout->assigned->first_name), + 'last_name'=> ($accessoryCheckout->assigned->last_name) ? e($accessoryCheckout->assigned->last_name) : null, + 'email'=> ($accessoryCheckout->assigned->email) ? e($accessoryCheckout->assigned->email) : null, + 'employee_number' => ($accessoryCheckout->assigned->employee_num) ? e($accessoryCheckout->assigned->employee_num) : null, + 'type' => 'user', + ]; + } + + return $accessoryCheckout->assigned ? [ + 'id' => $accessoryCheckout->assigned->id, + 'name' => e($accessoryCheckout->assigned->display_name), + 'type' => $accessoryCheckout->assignedType(), + ] : null; + } } diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 8a09cc8402..96d74827d2 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -205,11 +205,11 @@ class ActionlogsTransformer - public function transformCheckedoutActionlog (Collection $accessories_users, $total) + public function transformCheckedoutActionlog (Collection $accessories_checkout, $total) { $array = array(); - foreach ($accessories_users as $user) { + foreach ($accessories_checkout as $user) { $array[] = (new UsersTransformer)->transformUser($user); } return (new DatatablesTransformer)->transformDatatables($array, $total); diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index fa218da4d1..4fad9b9a68 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -45,6 +45,10 @@ class LicensesTransformer 'maintained' => ($license->maintained == 1) ? true : false, 'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id, 'name'=> e($license->supplier->name)] : null, 'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null, + 'created_by' => ($license->adminuser) ? [ + 'id' => (int) $license->adminuser->id, + 'name'=> e($license->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'), diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index 20d2584c31..c1366f67e6 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -253,9 +253,10 @@ class Accessory extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function users() + public function checkouts() { - return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id', 'created_at', 'note')->withTrashed(); + return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id') + ->with('assignedTo'); } /** @@ -267,7 +268,9 @@ class Accessory extends SnipeModel */ public function hasUsers() { - return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->count(); + return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id') + ->where('assigned_type', User::class) + ->count(); } /** @@ -338,15 +341,15 @@ class Accessory extends SnipeModel */ public function numCheckedOut() { - return $this->users_count ?? $this->users()->count(); + return $this->checkouts_count ?? $this->checkouts()->count(); } /** * Check how many items of an accessory remain. * - * In order to use this model method, you MUST call withCount('users as users_count') - * on the eloquent query in the controller, otherwise $this->users_count will be null and + * In order to use this model method, you MUST call withCount('checkouts as checkouts_count') + * on the eloquent query in the controller, otherwise $this->checkouts_count will be null and * bad things happen. * * @author [A. Gianotto] [] @@ -370,12 +373,12 @@ class Accessory extends SnipeModel */ public function declinedCheckout(User $declinedBy, $signature) { - if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) { + if (is_null($accessory_checkout = AccessoryCheckout::userAssigned()->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) { // Redirect to the accessory management page with error return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); } - $accessory_user->limit(1)->delete(); + $accessory_checkout->limit(1)->delete(); } /** diff --git a/app/Models/AccessoryCheckout.php b/app/Models/AccessoryCheckout.php new file mode 100755 index 0000000000..7f42b354e1 --- /dev/null +++ b/app/Models/AccessoryCheckout.php @@ -0,0 +1,148 @@ + accessory relationship + * + * @author [A. Kroeger] + * @since [v7.0.9] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function accessory() + { + return $this->hasOne(\App\Models\Accessory::class, 'accessory_id'); + } + + /** + * Establishes the accessory checkout -> user relationship + * + * @author [A. Kroeger] + * @since [v7.0.9] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function user() + { + return $this->hasOne(\App\Models\User::class, 'user_id'); + } + + /** + * Get the target this asset is checked out to + * + * @author [A. Kroeger] + * @since [v7.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assignedTo() + { + return $this->morphTo('assigned', 'assigned_type', 'assigned_to')->withTrashed(); + } + + /** + * Gets the lowercased name of the type of target the asset is assigned to + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return string + */ + public function assignedType() + { + return $this->assigned_type ? strtolower(class_basename($this->assigned_type)) : null; + } + + /** + * Determines whether the accessory is checked out to a user + * + * Even though we allow allow for checkout to things beyond users + * this method is an easy way of seeing if we are checked out to a user. + * + * @author [A. Kroeger] + * @since [v7.0] + */ + public function checkedOutToUser(): bool + { + return $this->assignedType() === Asset::USER; + } + + public function scopeUserAssigned(Builder $query): void + { + $query->where('assigned_type', '=', User::class); + } + + /** + * Run additional, advanced searches. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $terms The search terms + * @return \Illuminate\Database\Eloquent\Builder + */ + public function advancedTextSearch(Builder $query, array $terms) + { + + $userQuery = User::where(function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('first_name', 'like', $search_str) + ->orWhere('last_name', 'like', $search_str) + ->orWhere('note', 'like', $search_str); + } + })->select('id'); + + $locationQuery = Location::where(function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('name', 'like', $search_str); + } + })->select('id'); + + $assetQuery = Asset::where(function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('name', 'like', $search_str); + } + })->select('id'); + + $query->where(function ($query) use ($userQuery) { + $query->where('assigned_type', User::class) + ->whereIn('assigned_to', $userQuery); + })->orWhere(function($query) use ($locationQuery) { + $query->where('assigned_type', Location::class) + ->whereIn('assigned_to', $locationQuery); + })->orWhere(function($query) use ($assetQuery) { + $query->where('assigned_type', Asset::class) + ->whereIn('assigned_to', $assetQuery); + })->orWhere(function($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('note', 'like', $search_str); + } + }); + + return $query; + } + + +} diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index c1826a94d8..dfa4971367 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -7,7 +7,7 @@ use EasySlugger\Utf8Slugger; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\Rule; -use Schema; +use Illuminate\Support\Facades\Schema; use Watson\Validating\ValidatingTrait; class CustomField extends Model { diff --git a/app/Models/Depreciable.php b/app/Models/Depreciable.php index 7211358739..6139cda089 100644 --- a/app/Models/Depreciable.php +++ b/app/Models/Depreciable.php @@ -158,17 +158,20 @@ class Depreciable extends SnipeModel public function time_until_depreciated() { - // @link http://www.php.net/manual/en/class.datetime.php - $d1 = new \DateTime(); - $d2 = $this->depreciated_date(); + if ($this->depreciated_date()) { + // @link http://www.php.net/manual/en/class.datetime.php + $d1 = new \DateTime(); + $d2 = $this->depreciated_date(); - // @link http://www.php.net/manual/en/class.dateinterval.php - $interval = $d1->diff($d2); - if (! $interval->invert) { - return $interval; - } else { - return new \DateInterval('PT0S'); //null interval (zero seconds from now) + // @link http://www.php.net/manual/en/class.dateinterval.php + $interval = $d1->diff($d2); + if (! $interval->invert) { + return $interval; + } else { + return new \DateInterval('PT0S'); //null interval (zero seconds from now) + } } + return false; } public function depreciated_date() diff --git a/app/Models/License.php b/app/Models/License.php index d8bc3f03b1..554929c0ac 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -50,7 +50,7 @@ class License extends Depreciable 'category_id' => 'required|exists:categories,id', 'company_id' => 'integer|nullable', 'purchase_cost'=> 'numeric|nullable|gte:0', - 'purchase_date' => 'date_format:Y-m-d|nullable|max:10', + 'purchase_date' => 'date_format:Y-m-d|nullable|max:10|required_with:depreciation_id', 'expiration_date' => 'date_format:Y-m-d|nullable|max:10', 'termination_date' => 'date_format:Y-m-d|nullable|max:10', 'min_amt' => 'numeric|nullable|gte:0', @@ -736,4 +736,17 @@ class License extends Depreciable return $query->leftJoin('companies as companies', 'licenses.company_id', '=', 'companies.id')->select('licenses.*') ->orderBy('companies.name', $order); } + + /** + * 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) + { + 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); + } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index a93eb26561..c03b0d33c0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -331,7 +331,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo */ public function accessories() { - return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id') + return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_checkout', 'assigned_to', 'accessory_id') ->withPivot('id', 'created_at', 'note')->withTrashed()->orderBy('accessory_id'); } diff --git a/app/Presenters/AccessoryPresenter.php b/app/Presenters/AccessoryPresenter.php index fd6122cab7..4ff3c699c7 100644 --- a/app/Presenters/AccessoryPresenter.php +++ b/app/Presenters/AccessoryPresenter.php @@ -90,7 +90,7 @@ class AccessoryPresenter extends Presenter 'visible' => false, 'title' => trans('admin/accessories/general.remaining'), ],[ - 'field' => 'users_count', + 'field' => 'checkouts_count', 'searchable' => false, 'sortable' => true, 'visible' => true, diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php index 8ca8e120f2..1545cabd30 100644 --- a/app/Presenters/LicensePresenter.php +++ b/app/Presenters/LicensePresenter.php @@ -158,6 +158,13 @@ class LicensePresenter extends Presenter 'sortable' => true, 'visible' => false, 'title' => trans('general.order_number'), + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.admin'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', ], [ 'field' => 'created_at', 'searchable' => false, diff --git a/app/Providers/BladeServiceProvider.php b/app/Providers/BladeServiceProvider.php new file mode 100644 index 0000000000..024ee9d3dc --- /dev/null +++ b/app/Providers/BladeServiceProvider.php @@ -0,0 +1,25 @@ +alt_barcode_enabled) { if ($template->getSupport1DBarcode()) { - $barcode1DType = $settings->alt_barcode; + $barcode1DType = $settings->label2_1d_type; if ($barcode1DType != 'none') { $assetData->put('barcode1d', (object)[ 'type' => $barcode1DType, diff --git a/config/app.php b/config/app.php index 9de8b63c6d..060d82d525 100755 --- a/config/app.php +++ b/config/app.php @@ -313,6 +313,7 @@ return [ /* * Custom Service Providers... */ + App\Providers\BladeServiceProvider::class, App\Providers\LivewireServiceProvider::class, App\Providers\MacroServiceProvider::class, App\Providers\SamlServiceProvider::class, diff --git a/config/services.php b/config/services.php index 21acb6778a..07b1c1069f 100644 --- a/config/services.php +++ b/config/services.php @@ -44,12 +44,6 @@ return [ 'secret' => env('STRIPE_SECRET'), ], - 'baremetrics' => [ - 'enabled' => env('ENABLE_BMPAY', false), - 'app_key' => env('BMPAY_PUBLIC_KEY', null), - 'stripe_id' => env('BMPAY_STRIPE_ID', null), - ], - 'google' => [ 'maps_api_key' => env('GOOGLE_MAPS_API'), ], diff --git a/config/version.php b/config/version.php index 2713cc35cb..f419bf1bdf 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v7.0.9', - 'full_app_version' => 'v7.0.9 - build 14485-ga98ad76c6', - 'build_version' => '14485', + 'app_version' => 'v7.0.10', + 'full_app_version' => 'v7.0.10 - build 14684-gc2bcc2e2d', + 'build_version' => '14684', 'prerelease_version' => '', - 'hash_version' => 'ga98ad76c6', - 'full_hash' => 'v7.0.9-112-ga98ad76c6', + 'hash_version' => 'gc2bcc2e2d', + 'full_hash' => 'v7.0.10-311-gc2bcc2e2d', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/AccessoryFactory.php b/database/factories/AccessoryFactory.php index ce0d60cc18..356b367ec4 100644 --- a/database/factories/AccessoryFactory.php +++ b/database/factories/AccessoryFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use App\Models\Accessory; +use App\Models\AccessoryCheckout; use App\Models\Category; use App\Models\Location; use App\Models\Manufacturer; @@ -125,11 +126,12 @@ class AccessoryFactory extends Factory })->afterCreating(function ($accessory) { $user = User::factory()->create(); - $accessory->users()->attach($accessory->id, [ + $accessory->checkouts()->create([ 'accessory_id' => $accessory->id, - 'created_at' => now(), + 'created_at' => Carbon::now(), 'user_id' => $user->id, 'assigned_to' => $user->id, + 'assigned_type' => User::class, 'note' => '', ]); }); @@ -145,11 +147,12 @@ class AccessoryFactory extends Factory public function checkedOutToUser(User $user = null) { return $this->afterCreating(function (Accessory $accessory) use ($user) { - $accessory->users()->attach($accessory->id, [ + $accessory->checkouts()->create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), 'user_id' => 1, 'assigned_to' => $user->id ?? User::factory()->create()->id, + 'assigned_type' => User::class, ]); }); } diff --git a/database/factories/ComponentFactory.php b/database/factories/ComponentFactory.php index caac70078f..2557f29c77 100644 --- a/database/factories/ComponentFactory.php +++ b/database/factories/ComponentFactory.php @@ -2,10 +2,15 @@ namespace Database\Factories; +use App\Models\Accessory; +use App\Models\Asset; use App\Models\Category; use App\Models\Company; use App\Models\Component; +use App\Models\Consumable; use App\Models\Location; +use App\Models\User; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Supplier; @@ -97,5 +102,16 @@ class ComponentFactory extends Factory }); } + public function checkedOutToAsset(Asset $asset = null) + { + return $this->afterCreating(function (Component $component) use ($asset) { + $component->assets()->attach($component->id, [ + 'component_id' => $component->id, + 'created_at' => Carbon::now(), + 'user_id' => 1, + 'asset_id' => $asset->id ?? Asset::factory()->create()->id, + ]); + }); + } } diff --git a/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php b/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php new file mode 100644 index 0000000000..bde3bc2c28 --- /dev/null +++ b/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php @@ -0,0 +1,43 @@ +string('assigned_type')->nullable(); + } + }); + } + + DB::update('update '.DB::getTablePrefix().'accessories_checkout set assigned_type = \'App\\\\Models\\\\User\' where assigned_type is null', []); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (Schema::hasTable('accessories_checkout')) { + Schema::table('accessories_checkout', function (Blueprint $table) { + $table->dropColumn('assigned_type'); + }); + + Schema::rename('accessories_checkout', 'accessories_users'); + } + } +}; diff --git a/database/seeders/AccessorySeeder.php b/database/seeders/AccessorySeeder.php index 31f0c478d4..2330a99733 100644 --- a/database/seeders/AccessorySeeder.php +++ b/database/seeders/AccessorySeeder.php @@ -16,7 +16,7 @@ class AccessorySeeder extends Seeder public function run() { Accessory::truncate(); - DB::table('accessories_users')->truncate(); + DB::table('accessories_checkout')->truncate(); if (! Location::count()) { $this->call(LocationSeeder::class); diff --git a/resources/assets/less/overrides.less b/resources/assets/less/overrides.less index 12bce00bb6..8ef34bb3e2 100644 --- a/resources/assets/less/overrides.less +++ b/resources/assets/less/overrides.less @@ -714,6 +714,11 @@ th.css-location > .th-inner::before { margin-top:50px } } +@media screen and (max-width: 1318px) and (min-width: 1200px){ + .box{ + height:170px; + } +} .ellipsis { overflow: hidden; diff --git a/resources/lang/en-US/admin/settings/message.php b/resources/lang/en-US/admin/settings/message.php index c9b0f34217..24e2d292ca 100644 --- a/resources/lang/en-US/admin/settings/message.php +++ b/resources/lang/en-US/admin/settings/message.php @@ -14,6 +14,9 @@ return [ 'restore_warning' => 'Yes, restore it. I acknowledge that this will overwrite any existing data currently in the database. This will also log out all of your existing users (including you).', 'restore_confirm' => 'Are you sure you wish to restore your database from :filename?' ], + 'restore' => [ + 'success' => 'Your system backup has been restored. Please login again.' + ], 'purge' => [ 'error' => 'An error has occurred while purging. ', 'validation_failed' => 'Your purge confirmation is incorrect. Please type the word "DELETE" in the confirmation box.', diff --git a/resources/lang/en-US/validation.php b/resources/lang/en-US/validation.php index b33548e2ff..c358700e95 100644 --- a/resources/lang/en-US/validation.php +++ b/resources/lang/en-US/validation.php @@ -188,6 +188,8 @@ return [ 'hashed_pass' => 'Your current password is incorrect', 'dumbpwd' => 'That password is too common.', 'statuslabel_type' => 'You must select a valid status label type', + 'custom_field_not_found' => 'This field does not seem to exist, please double check your custom field names.', + 'custom_field_not_found_on_model' => 'This field seems to exist, but is not available on this Asset Model\'s fieldset.', // date_format validation with slightly less stupid messages. It duplicates a lot, but it gets the job done :( // We use this because the default error message for date_format is reflects php Y-m-d, which non-PHP diff --git a/resources/lang/si-LK/general.php b/resources/lang/si-LK/general.php index 4c0d64fd9d..0063808f92 100644 --- a/resources/lang/si-LK/general.php +++ b/resources/lang/si-LK/general.php @@ -556,7 +556,7 @@ return [ 'something_went_wrong' => 'Something went wrong with your request.', 'close' => 'Close', 'expires' => 'Expires', - 'map_fields'=> 'Map :item_type Field', + 'map_fields'=> 'Map :item_type Fields', 'remaining_var' => ':count Remaining', ]; diff --git a/resources/views/accessories/checkin.blade.php b/resources/views/accessories/checkin.blade.php index 7d1ef13262..cceb8b895b 100755 --- a/resources/views/accessories/checkin.blade.php +++ b/resources/views/accessories/checkin.blade.php @@ -71,11 +71,15 @@ - + diff --git a/resources/views/accessories/checkout.blade.php b/resources/views/accessories/checkout.blade.php index 71035a87c1..0fe6abec36 100755 --- a/resources/views/accessories/checkout.blade.php +++ b/resources/views/accessories/checkout.blade.php @@ -66,7 +66,14 @@ - @include ('partials.forms.edit.user-select', ['translated_name' => trans('general.select_user'), 'fieldname' => 'assigned_to', 'required'=> 'true']) + @include ('partials.forms.checkout-selector', ['user_select' => 'true','asset_select' => 'true', 'location_select' => 'true']) + + @include ('partials.forms.edit.user-select', ['translated_name' => trans('general.select_user'), 'fieldname' => 'assigned_user', 'required'=> 'true']) + + + @include ('partials.forms.edit.asset-select', ['translated_name' => trans('general.asset'), 'fieldname' => 'assigned_asset', 'unselect' => 'true', 'style' => 'display:none;', 'required'=>'true']) + + @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'assigned_location', 'style' => 'display:none;', 'required'=>'true'])
@@ -114,10 +121,16 @@
- + diff --git a/resources/views/accessories/edit.blade.php b/resources/views/accessories/edit.blade.php index 068fddc266..8e0503400f 100644 --- a/resources/views/accessories/edit.blade.php +++ b/resources/views/accessories/edit.blade.php @@ -4,6 +4,11 @@ 'helpPosition' => 'right', 'helpText' => trans('help.accessories'), 'formAction' => (isset($item->id)) ? route('accessories.update', ['accessory' => $item->id]) : route('accessories.store'), + 'index_route' => 'accessories.index', + 'options' => [ + 'index' => trans('admin/hardware/form.redirect_to_all', ['type' => 'accessories']), + 'item' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.accessory')]), + ] ]) {{-- Page content --}} diff --git a/resources/views/accessories/view.blade.php b/resources/views/accessories/view.blade.php index e42439bd86..f3add91d40 100644 --- a/resources/views/accessories/view.blade.php +++ b/resources/views/accessories/view.blade.php @@ -68,9 +68,9 @@
name) }}-checkouts-{{ date('Y-m-d') }}", "ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"] }'> - + @@ -314,7 +314,7 @@ {{ trans('general.checked_out') }}
- {{ $accessory->users_count }} + {{ $accessory->checkouts_count }}
@@ -337,7 +337,7 @@ @endcan @can('delete', $accessory) - @if ($accessory->users_count == 0) + @if ($accessory->checkouts_count == 0)
+ +
+ + + + \ No newline at end of file diff --git a/resources/views/components/checkin.blade.php b/resources/views/components/checkin.blade.php index e195685d2a..d838744ffb 100644 --- a/resources/views/components/checkin.blade.php +++ b/resources/views/components/checkin.blade.php @@ -56,10 +56,14 @@ {!! $errors->first('note', '') !!} - + diff --git a/resources/views/components/checkout.blade.php b/resources/views/components/checkout.blade.php index f6befa2736..39890a1938 100644 --- a/resources/views/components/checkout.blade.php +++ b/resources/views/components/checkout.blade.php @@ -54,10 +54,16 @@ - + diff --git a/resources/views/components/edit.blade.php b/resources/views/components/edit.blade.php index 5279f03990..ce77b5130d 100644 --- a/resources/views/components/edit.blade.php +++ b/resources/views/components/edit.blade.php @@ -4,6 +4,11 @@ 'helpPosition' => 'right', 'helpText' => trans('help.components'), 'formAction' => (isset($item->id)) ? route('components.update', ['component' => $item->id]) : route('components.store'), + 'index_route' => 'components.index', + 'options' => [ + 'index' => trans('admin/hardware/form.redirect_to_all', ['type' => 'components']), + 'item' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.component')]), + ] ]) diff --git a/resources/views/consumables/checkout.blade.php b/resources/views/consumables/checkout.blade.php index 29b68b6ce7..bb3023290f 100644 --- a/resources/views/consumables/checkout.blade.php +++ b/resources/views/consumables/checkout.blade.php @@ -106,10 +106,14 @@ - + diff --git a/resources/views/consumables/edit.blade.php b/resources/views/consumables/edit.blade.php index f32e74b196..300a7114d9 100644 --- a/resources/views/consumables/edit.blade.php +++ b/resources/views/consumables/edit.blade.php @@ -4,6 +4,11 @@ 'helpPosition' => 'right', 'helpText' => trans('help.consumables'), 'formAction' => (isset($item->id)) ? route('consumables.update', ['consumable' => $item->id]) : route('consumables.store'), + 'index_route' => 'consumables.index', + 'options' => [ + 'index' => trans('admin/hardware/form.redirect_to_all', ['type' => 'consumables']), + 'item' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.consumable')]), + ] ]) {{-- Page content --}} @section('inputFields') diff --git a/resources/views/hardware/checkin.blade.php b/resources/views/hardware/checkin.blade.php index 7b52cf8614..efd0502669 100755 --- a/resources/views/hardware/checkin.blade.php +++ b/resources/views/hardware/checkin.blade.php @@ -2,132 +2,144 @@ {{-- Page title --}} @section('title') - {{ trans('admin/hardware/general.checkin') }} - @parent + {{ trans('admin/hardware/general.checkin') }} + @parent @stop {{-- Page content --}} @section('content') - + .input-group { + padding-left: 0px !important; + } + -
- -
-
-
-

- {{ trans('admin/hardware/form.tag') }} - {{ $asset->asset_tag }} -

-
+
+ +
+
+
+

+ {{ trans('admin/hardware/form.tag') }} + {{ $asset->asset_tag }} +

+
-
-
+
+
- @if ($backto == 'user') -
- @else - - @endif - {{csrf_field()}} + @if ($backto == 'user') + + @else + + @endif + {{csrf_field()}} - -
- -
+ +
+ +
-

- @if (($asset->model) && ($asset->model->name)) - {{ $asset->model->name }} - @else - +

+ @if (($asset->model) && ($asset->model->name)) + {{ $asset->model->name }} + @else + {{ trans('admin/hardware/general.model_invalid')}} - {{ trans('admin/hardware/general.model_invalid_fix')}} - - {{ trans('admin/hardware/general.edit') }} - - @endif -

+ {{ trans('admin/hardware/general.model_invalid_fix')}} + + {{ trans('admin/hardware/general.edit') }} + + @endif +

-
-
+
+
- -
- -
- - {!! $errors->first('name', '') !!} -
-
+ +
+ +
+ + {!! $errors->first('name', '') !!} +
+
- -
- -
- {{ Form::select('status_id', $statusLabel_list, '', array('class'=>'select2', 'style'=>'width:100%','id' =>'modal-statuslabel_types', 'aria-label'=>'status_id')) }} - {!! $errors->first('status_id', '') !!} -
-
+ +
+ +
+ {{ Form::select('status_id', $statusLabel_list, '', array('class'=>'select2', 'style'=>'width:100%','id' =>'modal-statuslabel_types', 'aria-label'=>'status_id')) }} + {!! $errors->first('status_id', '') !!} +
+
- @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id', 'help_text' => ($asset->defaultLoc) ? trans('general.checkin_to_diff_location', ['default_location' => $asset->defaultLoc->name]) : null, 'hide_location_radio' => true]) + @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id', 'help_text' => ($asset->defaultLoc) ? trans('general.checkin_to_diff_location', ['default_location' => $asset->defaultLoc->name]) : null, 'hide_location_radio' => true]) - -
- + +
+ -
-
-
- - -
- {!! $errors->first('checkin_at', '') !!} -
-
-
+
+
+
+ + +
+ {!! $errors->first('checkin_at', '') !!} +
+
+
- -
- -
- - {!! $errors->first('note', '') !!} -
-
+ +
+ +
+ + {!! $errors->first('note', '') !!} +
+
- @include ('partials.forms.redirect_submit_options', - [ - 'route' => 'hardware.index', - 'table_name' => $table_name, - 'type'=> ($asset->model ? $asset->model->name : trans('general.asset_model')), - 'checkin' => true - ]) - + + -
+
+
-
- @stop \ No newline at end of file diff --git a/resources/views/hardware/checkout.blade.php b/resources/views/hardware/checkout.blade.php index 4a1cdf5749..f91e060368 100755 --- a/resources/views/hardware/checkout.blade.php +++ b/resources/views/hardware/checkout.blade.php @@ -25,7 +25,7 @@

{{ trans('admin/hardware/form.tag') }} {{ $asset->asset_tag }}

- {{csrf_field()}} + {{csrf_field()}} @if ($asset->company && $asset->company->name)
- + {!! $errors->first('name', '') !!}
@@ -86,26 +87,30 @@
- @include ('partials.forms.checkout-selector', ['user_select' => 'true','asset_select' => 'true', 'location_select' => 'true']) + @include ('partials.forms.checkout-selector', ['user_select' => 'true','asset_select' => 'true', 'location_select' => 'true']) - @include ('partials.forms.edit.user-select', ['translated_name' => trans('general.user'), 'fieldname' => 'assigned_user', 'required'=>'true']) + @include ('partials.forms.edit.user-select', ['translated_name' => trans('general.user'), 'fieldname' => 'assigned_user', 'required'=>'true']) - - @include ('partials.forms.edit.asset-select', ['translated_name' => trans('general.asset'), 'fieldname' => 'assigned_asset', 'unselect' => 'true', 'style' => 'display:none;', 'required'=>'true']) + + @include ('partials.forms.edit.asset-select', ['translated_name' => trans('general.asset'), 'fieldname' => 'assigned_asset', 'unselect' => 'true', 'style' => 'display:none;', 'required'=>'true']) - @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'assigned_location', 'style' => 'display:none;', 'required'=>'true']) + @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'assigned_location', 'style' => 'display:none;', 'required'=>'true']) - +
-
- - +
+ +
{!! $errors->first('checkout_at', '') !!}
@@ -118,9 +123,13 @@
-
- - +
+ +
{!! $errors->first('expected_checkin', '') !!}
@@ -132,7 +141,8 @@ {{ trans('general.notes') }}
- + {!! $errors->first('note', '') !!}
@@ -164,13 +174,19 @@ @endif
- @include ('partials.forms.redirect_submit_options', - [ - 'route' => 'hardware.index', - 'table_name' => $table_name, - 'type'=> ($asset->model ? $asset->model->name : trans('general.asset_model')), - 'checkin' => false - ]) + + +
diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index d2d9cc8f03..f31326cc4d 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -6,6 +6,11 @@ 'helpText' => trans('help.assets'), 'helpPosition' => 'right', 'formAction' => ($item->id) ? route('hardware.update', ['hardware' => $item->id]) : route('hardware.store'), + 'index_route' => 'hardware.index', + 'options' => [ + 'index' => trans('admin/hardware/form.redirect_to_all', ['type' => 'assets']), + 'item' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset')]), + ] ]) @@ -130,12 +135,10 @@ - -
-
@endcan @@ -297,17 +297,40 @@ @endif
- @endif -

+ @if ($snipeSettings->qr_code=='1') +
+ QR code for {{ $asset->getDisplayNameAttribute() }} +
+ @endif + +

+ + +
+ @if ($asset->asset_tag) +
+
+ {{ trans('admin/hardware/form.tag') }} +
+
+ {{ $asset->asset_tag }} + + +
+
+ @endif + @if ($asset->deleted_at!='')
diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index ee235d358e..839d769051 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -1136,8 +1136,5 @@ dir="{{ Helper::determineLanguageDirection() }}"> @endif - @include('partials.bpay') - - diff --git a/resources/views/layouts/edit-form.blade.php b/resources/views/layouts/edit-form.blade.php index 791590141c..2c582ec4b5 100644 --- a/resources/views/layouts/edit-form.blade.php +++ b/resources/views/layouts/edit-form.blade.php @@ -66,7 +66,11 @@ {{ csrf_field() }} @yield('inputFields') - @include('partials.forms.edit.submit') +
diff --git a/resources/views/licenses/checkin.blade.php b/resources/views/licenses/checkin.blade.php index 8eeea6f9c4..5ef9159549 100755 --- a/resources/views/licenses/checkin.blade.php +++ b/resources/views/licenses/checkin.blade.php @@ -56,10 +56,14 @@ {!! $errors->first('notes', '') !!}
- + diff --git a/resources/views/licenses/checkout.blade.php b/resources/views/licenses/checkout.blade.php index e85c677420..bfd4773099 100755 --- a/resources/views/licenses/checkout.blade.php +++ b/resources/views/licenses/checkout.blade.php @@ -105,10 +105,15 @@ @endif - + diff --git a/resources/views/licenses/edit.blade.php b/resources/views/licenses/edit.blade.php index 450e4357a4..b8429a2123 100755 --- a/resources/views/licenses/edit.blade.php +++ b/resources/views/licenses/edit.blade.php @@ -3,6 +3,11 @@ 'updateText' => trans('admin/licenses/form.update'), 'topSubmit' => true, 'formAction' => ($item->id) ? route('licenses.update', ['license' => $item->id]) : route('licenses.store'), + 'index_route' => 'licenses.index', + 'options' => [ + 'index' => trans('admin/hardware/form.redirect_to_all', ['type' => 'licenses']), + 'item' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.license')]), + ] ]) {{-- Page content --}} diff --git a/resources/views/partials/bpay.blade.php b/resources/views/partials/bpay.blade.php deleted file mode 100644 index 0cf0be5403..0000000000 --- a/resources/views/partials/bpay.blade.php +++ /dev/null @@ -1,15 +0,0 @@ -@can('admin') - @if ((config('services.baremetrics.enabled')=='true') && (config('services.baremetrics.app_key')) && (config('services.baremetrics.stripe_id'))) - - @else - @endif -@endcan diff --git a/resources/views/partials/forms/redirect_submit_options.blade.php b/resources/views/partials/forms/redirect_submit_options.blade.php deleted file mode 100644 index c960dbb860..0000000000 --- a/resources/views/partials/forms/redirect_submit_options.blade.php +++ /dev/null @@ -1,36 +0,0 @@ - - - - \ No newline at end of file diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 814c2f28eb..e3f691f158 100755 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -596,9 +596,14 @@
{{ trans('general.user') }}{{ trans('general.checked_out_to') }} {{ trans('general.notes') }} {{ trans('admin/hardware/table.checkout_date') }} {{ trans('table.actions') }}
- + @@ -673,7 +678,7 @@ $(document).ready(function() { 'bind': 'click', 'passwordElement': '#password', 'displayElement': '#generated-password', - 'passwordLength': 16, + 'passwordLength': {{ ($settings->pwd_secure_min + 5) }}, 'uppercase': true, 'lowercase': true, 'numbers': true, diff --git a/routes/api.php b/routes/api.php index b5311aa982..5bcf418c5e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -576,6 +576,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi // this would probably keep working with the resource route group, but the general practice is for // the model name to be the parameter - and i think it's a good differentiation in the code while we convert the others. Route::patch('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.update'); + Route::put('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.put-update'); Route::resource('hardware', Api\AssetsController::class, @@ -712,7 +713,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi Route::get('{location}/assets', [ Api\LocationsController::class, - 'getDataViewAssets' + 'assets' ] )->name('api.locations.viewassets'); diff --git a/tests/Feature/Assets/Ui/EditAssetTest.php b/tests/Feature/Assets/Ui/EditAssetTest.php index 9e58be2685..2c19e768b4 100644 --- a/tests/Feature/Assets/Ui/EditAssetTest.php +++ b/tests/Feature/Assets/Ui/EditAssetTest.php @@ -3,17 +3,65 @@ namespace Feature\Assets\Ui; use App\Models\Asset; +use App\Models\AssetModel; +use App\Models\StatusLabel; use App\Models\User; use Tests\TestCase; class EditAssetTest extends TestCase { + + public function testPermissionRequiredToViewLicense() + { + $asset = Asset::factory()->create(); + $this->actingAs(User::factory()->create()) + ->get(route('hardware.edit', $asset)) + ->assertForbidden(); + } + public function testPageCanBeAccessed(): void { $asset = Asset::factory()->create(); $user = User::factory()->editAssets()->create(); $response = $this->actingAs($user)->get(route('hardware.edit', $asset->id)); - $response->assertStatus(200); } + + public function testAssetEditPostIsRedirectedIfRedirectSelectionIsIndex() + { + $asset = Asset::factory()->assignedToUser()->create(); + + $this->actingAs(User::factory()->viewAssets()->editAssets()->create()) + ->from(route('hardware.edit', $asset)) + ->put(route('hardware.update', $asset), + [ + 'redirect_option' => 'index', + 'name' => 'New name', + 'asset_tags' => 'New Asset Tag', + 'status_id' => StatusLabel::factory()->create()->id, + 'model_id' => AssetModel::factory()->create()->id, + ]) + ->assertStatus(302) + ->assertRedirect(route('hardware.index')); + $this->assertDatabaseHas('assets', ['asset_tag' => 'New Asset Tag']); + } + public function testAssetEditPostIsRedirectedIfRedirectSelectionIsItem() + { + $asset = Asset::factory()->create(); + + $this->actingAs(User::factory()->viewAssets()->editAssets()->create()) + ->from(route('hardware.edit', $asset)) + ->put(route('hardware.update', $asset), [ + 'redirect_option' => 'item', + 'name' => 'New name', + 'asset_tags' => 'New Asset Tag', + 'status_id' => StatusLabel::factory()->create()->id, + 'model_id' => AssetModel::factory()->create()->id, + ]) + ->assertStatus(302) + ->assertRedirect(route('hardware.show', ['hardware' => $asset->id])); + + $this->assertDatabaseHas('assets', ['asset_tag' => 'New Asset Tag']); + } + } diff --git a/tests/Feature/Checkins/Ui/AccessoryCheckinTest.php b/tests/Feature/Checkins/Ui/AccessoryCheckinTest.php index 1213741bcc..7a99b2ab56 100644 --- a/tests/Feature/Checkins/Ui/AccessoryCheckinTest.php +++ b/tests/Feature/Checkins/Ui/AccessoryCheckinTest.php @@ -17,7 +17,7 @@ class AccessoryCheckinTest extends TestCase $accessory = Accessory::factory()->checkedOutToUser()->create(); $this->actingAs(User::factory()->create()) - ->post(route('accessories.checkin.store', $accessory->users->first()->pivot->id)) + ->post(route('accessories.checkin.store', $accessory->checkouts->first()->id)) ->assertForbidden(); } @@ -28,12 +28,12 @@ class AccessoryCheckinTest extends TestCase $user = User::factory()->create(); $accessory = Accessory::factory()->checkedOutToUser($user)->create(); - $this->assertTrue($accessory->users->contains($user)); + $this->assertTrue($accessory->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); $this->actingAs(User::factory()->checkinAccessories()->create()) - ->post(route('accessories.checkin.store', $accessory->users->first()->pivot->id)); + ->post(route('accessories.checkin.store', $accessory->checkouts->first()->id)); - $this->assertFalse($accessory->fresh()->users->contains($user)); + $this->assertFalse($accessory->fresh()->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); Event::assertDispatched(CheckoutableCheckedIn::class, 1); } diff --git a/tests/Feature/Checkins/Ui/AssetCheckinTest.php b/tests/Feature/Checkins/Ui/AssetCheckinTest.php index cebd5010f1..f412d4439e 100644 --- a/tests/Feature/Checkins/Ui/AssetCheckinTest.php +++ b/tests/Feature/Checkins/Ui/AssetCheckinTest.php @@ -196,4 +196,31 @@ class AssetCheckinTest extends TestCase ->assertSessionHas('error') ->assertRedirect(route('hardware.show', ['hardware' => $asset->id])); } + + public function testAssetCheckinPagePostIsRedirectedIfRedirectSelectionIsIndex() + { + $asset = Asset::factory()->assignedToUser()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.index')) + ->post(route('hardware.checkin.store', $asset), [ + 'redirect_option' => 'index', + ]) + ->assertStatus(302) + ->assertRedirect(route('hardware.index')); + } + + public function testAssetCheckinPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $asset = Asset::factory()->assignedToUser()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.index')) + ->post(route('hardware.checkin.store', $asset), [ + 'redirect_option' => 'item', + ]) + ->assertStatus(302) + ->assertSessionHasNoErrors() + ->assertRedirect(route('hardware.show', ['hardware' => $asset->id])); + } } diff --git a/tests/Feature/Checkins/Ui/ComponentCheckinTest.php b/tests/Feature/Checkins/Ui/ComponentCheckinTest.php new file mode 100644 index 0000000000..1213d65252 --- /dev/null +++ b/tests/Feature/Checkins/Ui/ComponentCheckinTest.php @@ -0,0 +1,51 @@ +actingAs(User::factory()->create()) + ->post(route('components.checkin.store', [ + 'componentID' => Component::factory()->checkedOutToAsset()->create()->id, + ])) + ->assertForbidden(); + } + + + public function testComponentCheckinPagePostIsRedirectedIfRedirectSelectionIsIndex() + { + $component = Component::factory()->checkedOutToAsset()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('components.index')) + ->post(route('components.checkin.store', $component), [ + 'redirect_option' => 'index', + 'checkin_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('components.index')); + } + + public function testComponentCheckinPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $component = Component::factory()->checkedOutToAsset()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('components.index')) + ->post(route('components.checkin.store', $component), [ + 'redirect_option' => 'item', + 'checkin_qty' => 1, + ]) + ->assertStatus(302) + ->assertSessionHasNoErrors() + ->assertRedirect(route('components.show', ['component' => $component->id])); + } + + +} diff --git a/tests/Feature/Checkins/Ui/LicenseCheckinTest.php b/tests/Feature/Checkins/Ui/LicenseCheckinTest.php new file mode 100644 index 0000000000..e087cb442d --- /dev/null +++ b/tests/Feature/Checkins/Ui/LicenseCheckinTest.php @@ -0,0 +1,21 @@ +actingAs(User::factory()->create()) + ->post(route('licenses.checkin.save', [ + 'licenseId' => LicenseSeat::factory()->assignedToUser()->create()->id, + ])) + ->assertForbidden(); + } + + +} diff --git a/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php b/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php index 3f99f67ebf..2b37797fb6 100644 --- a/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php +++ b/tests/Feature/Checkouts/Api/AccessoryCheckoutTest.php @@ -22,7 +22,7 @@ class AccessoryCheckoutTest extends TestCase { $this->actingAsForApi(User::factory()->checkoutAccessories()->create()) ->postJson(route('api.accessories.checkout', Accessory::factory()->create()), [ - // missing assigned_to + // missing assigned_user, assigned_location, assigned_asset ]) ->assertStatusMessageIs('error'); } @@ -31,7 +31,8 @@ class AccessoryCheckoutTest extends TestCase { $this->actingAsForApi(User::factory()->checkoutAccessories()->create()) ->postJson(route('api.accessories.checkout', Accessory::factory()->withoutItemsRemaining()->create()), [ - 'assigned_to' => User::factory()->create()->id, + 'assigned_user' => User::factory()->create()->id, + 'checkout_to_type' => 'user' ]) ->assertOk() ->assertStatusMessageIs('error') @@ -65,7 +66,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAsForApi($admin) ->postJson(route('api.accessories.checkout', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user' ]) ->assertOk() ->assertStatusMessageIs('success') @@ -73,7 +75,7 @@ class AccessoryCheckoutTest extends TestCase ->assertJson(['messages' => trans('admin/accessories/message.checkout.success')]) ->json(); - $this->assertTrue($accessory->users->contains($user)); + $this->assertTrue($accessory->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); $this->assertEquals( 1, @@ -96,7 +98,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAsForApi($admin) ->postJson(route('api.accessories.checkout', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', 'checkout_qty' => 2, ]) ->assertOk() @@ -105,7 +108,7 @@ class AccessoryCheckoutTest extends TestCase ->assertJson(['messages' => trans('admin/accessories/message.checkout.success')]) ->json(); - $this->assertTrue($accessory->users->contains($user)); + $this->assertTrue($accessory->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); $this->assertEquals( 1, @@ -128,7 +131,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAsForApi(User::factory()->checkoutAccessories()->create()) ->postJson(route('api.accessories.checkout', $accessory), [ - 'assigned_to' => 'invalid-user-id', + 'assigned_user' => 'invalid-user-id', + 'checkout_to_type' => 'user', 'note' => 'oh hi there', ]) ->assertOk() @@ -136,7 +140,7 @@ class AccessoryCheckoutTest extends TestCase ->assertStatus(200) ->json(); - $this->assertFalse($accessory->users->contains($user)); + $this->assertFalse($accessory->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); } public function testUserSentNotificationUponCheckout() @@ -148,7 +152,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAsForApi(User::factory()->checkoutAccessories()->create()) ->postJson(route('api.accessories.checkout', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', ]); Notification::assertSentTo($user, CheckoutAccessoryNotification::class); @@ -162,7 +167,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAsForApi($actor) ->postJson(route('api.accessories.checkout', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', 'note' => 'oh hi there', ]); diff --git a/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php b/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php index e0af379db0..d4818ffc4b 100644 --- a/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php @@ -4,6 +4,8 @@ namespace Tests\Feature\Checkouts\Ui; use App\Models\Accessory; use App\Models\Actionlog; +use App\Models\Asset; +use App\Models\Location; use App\Models\User; use App\Notifications\CheckoutAccessoryNotification; use Illuminate\Support\Facades\Notification; @@ -40,7 +42,8 @@ class AccessoryCheckoutTest extends TestCase $response = $this->actingAs(User::factory()->viewAccessories()->checkoutAccessories()->create()) ->from(route('accessories.checkout.show', $accessory)) ->post(route('accessories.checkout.store', $accessory), [ - 'assigned_to' => User::factory()->create()->id, + 'assigned_user' => User::factory()->create()->id, + 'checkout_to_type' => 'user', ]) ->assertStatus(302) ->assertSessionHas('errors') @@ -56,11 +59,12 @@ class AccessoryCheckoutTest extends TestCase $this->actingAs(User::factory()->checkoutAccessories()->create()) ->post(route('accessories.checkout.store', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', 'note' => 'oh hi there', ]); - $this->assertTrue($accessory->users->contains($user)); + $this->assertTrue($accessory->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); $this->assertDatabaseHas('action_logs', [ 'action_type' => 'checkout', @@ -80,12 +84,13 @@ class AccessoryCheckoutTest extends TestCase $this->actingAs(User::factory()->checkoutAccessories()->create()) ->from(route('accessories.checkout.show', $accessory)) ->post(route('accessories.checkout.store', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', 'checkout_qty' => 3, 'note' => 'oh hi there', ]); - $this->assertTrue($accessory->users->contains($user)); + $this->assertTrue($accessory->checkouts()->where('assigned_type', User::class)->where('assigned_to', $user->id)->count() > 0); $this->assertDatabaseHas('action_logs', [ 'action_type' => 'checkout', @@ -97,6 +102,58 @@ class AccessoryCheckoutTest extends TestCase ]); } + public function testAccessoryCanBeCheckedOutToLocationWithQuantity() + { + $accessory = Accessory::factory()->create(['qty'=>5]); + $location = Location::factory()->create(); + + $this->actingAs(User::factory()->checkoutAccessories()->create()) + ->from(route('accessories.checkout.show', $accessory)) + ->post(route('accessories.checkout.store', $accessory), [ + 'assigned_location' => $location->id, + 'checkout_to_type' => 'location', + 'checkout_qty' => 3, + 'note' => 'oh hi there', + ]); + + $this->assertTrue($accessory->checkouts()->where('assigned_type', Location::class)->where('assigned_to', $location->id)->count() > 0); + + $this->assertDatabaseHas('action_logs', [ + 'action_type' => 'checkout', + 'target_id' => $location->id, + 'target_type' => Location::class, + 'item_id' => $accessory->id, + 'item_type' => Accessory::class, + 'note' => 'oh hi there', + ]); + } + + public function testAccessoryCanBeCheckedOutToAssetWithQuantity() + { + $accessory = Accessory::factory()->create(['qty'=>5]); + $asset = Asset::factory()->create(); + + $this->actingAs(User::factory()->checkoutAccessories()->create()) + ->from(route('accessories.checkout.show', $accessory)) + ->post(route('accessories.checkout.store', $accessory), [ + 'assigned_asset' => $asset->id, + 'checkout_to_type' => 'asset', + 'checkout_qty' => 3, + 'note' => 'oh hi there', + ]); + + $this->assertTrue($accessory->checkouts()->where('assigned_type', Asset::class)->where('assigned_to', $asset->id)->count() > 0); + + $this->assertDatabaseHas('action_logs', [ + 'action_type' => 'checkout', + 'target_id' => $asset->id, + 'target_type' => Asset::class, + 'item_id' => $accessory->id, + 'item_type' => Accessory::class, + 'note' => 'oh hi there', + ]); + } + public function testUserSentNotificationUponCheckout() { Notification::fake(); @@ -107,7 +164,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAs(User::factory()->checkoutAccessories()->create()) ->from(route('accessories.checkout.show', $accessory)) ->post(route('accessories.checkout.store', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', ]); Notification::assertSentTo($user, CheckoutAccessoryNotification::class); @@ -122,7 +180,8 @@ class AccessoryCheckoutTest extends TestCase $this->actingAs($actor) ->from(route('accessories.checkout.show', $accessory)) ->post(route('accessories.checkout.store', $accessory), [ - 'assigned_to' => $user->id, + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', 'note' => 'oh hi there', ]); @@ -140,4 +199,54 @@ class AccessoryCheckoutTest extends TestCase 'Log entry either does not exist or there are more than expected' ); } + + public function testAccessoryCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex() + { + $accessory = Accessory::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('accessories.index')) + ->post(route('accessories.checkout.store', $accessory), [ + 'assigned_user' => User::factory()->create()->id, + 'checkout_to_type' => 'user', + 'redirect_option' => 'index', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('accessories.index')); + } + + public function testAccessoryCheckoutPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $accessory = Accessory::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('accessories.index')) + ->post(route('accessories.checkout.store' , $accessory), [ + 'assigned_user' => User::factory()->create()->id, + 'checkout_to_type' => 'user', + 'redirect_option' => 'item', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertSessionHasNoErrors() + ->assertRedirect(route('accessories.show', ['accessory' => $accessory->id])); + } + + public function testAccessoryCheckoutPagePostIsRedirectedIfRedirectSelectionIsTarget() + { + $user = User::factory()->create(); + $accessory = Accessory::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('accessories.index')) + ->post(route('accessories.checkout.store' , $accessory), [ + 'assigned_user' => $user->id, + 'checkout_to_type' => 'user', + 'redirect_option' => 'target', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('users.show', ['user' => $user])); + } } diff --git a/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php b/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php index eed0908c1d..165c6a4194 100644 --- a/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Checkouts\Ui; use App\Events\CheckoutableCheckedOut; +use App\Models\Accessory; use App\Models\Asset; use App\Models\Company; use App\Models\LicenseSeat; @@ -251,20 +252,85 @@ class AssetCheckoutTest extends TestCase ->assertRedirect(route('hardware.show',['hardware' => $asset->id])); } - public function testAssetCheckoutPagePostIsRedirectedIfModelIsInvalid() + public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex() { $asset = Asset::factory()->create(); - $asset->model_id = 0; - $asset->forceSave(); - $user = User::factory()->create(); - + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.checkout.create', $asset)) ->post(route('hardware.checkout.store', $asset), [ 'checkout_to_type' => 'user', - 'assigned_user' => $user->id, + 'assigned_user' => User::factory()->create()->id, + 'redirect_option' => 'index', ]) ->assertStatus(302) - ->assertSessionHas('error') + ->assertRedirect(route('hardware.index')); + } + + public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $asset = Asset::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.checkout.create', $asset)) + ->post(route('hardware.checkout.store' , $asset), [ + 'checkout_to_type' => 'user', + 'assigned_user' => User::factory()->create()->id, + 'redirect_option' => 'item', + ]) + ->assertStatus(302) + ->assertSessionHasNoErrors() ->assertRedirect(route('hardware.show', ['hardware' => $asset->id])); } + + public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsUserTarget() + { + $user = User::factory()->create(); + $asset = Asset::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.checkout.create', $asset)) + ->post(route('hardware.checkout.store' , $asset), [ + 'checkout_to_type' => 'user', + 'assigned_user' => $user->id, + 'redirect_option' => 'target', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('users.show', ['user' => $user])); + } + + public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsAssetTarget() + { + $target = Asset::factory()->create(); + $asset = Asset::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.checkout.create', $asset)) + ->post(route('hardware.checkout.store' , $asset), [ + 'checkout_to_type' => 'asset', + 'assigned_asset' => $target->id, + 'redirect_option' => 'target', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('hardware.show', ['hardware' => $target])); + } + + public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsLocationTarget() + { + $target = Location::factory()->create(); + $asset = Asset::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('hardware.checkout.create', $asset)) + ->post(route('hardware.checkout.store' , $asset), [ + 'checkout_to_type' => 'location', + 'assigned_location' => $target->id, + 'redirect_option' => 'target', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('locations.show', ['location' => $target])); + } } diff --git a/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php b/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php new file mode 100644 index 0000000000..da285bebbb --- /dev/null +++ b/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php @@ -0,0 +1,68 @@ +actingAs(User::factory()->create()) + ->post(route('components.checkout.store', [ + 'componentID' => Component::factory()->checkedOutToAsset()->create()->id, + ])) + ->assertForbidden(); + } + + public function testComponentCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex() + { + $component = Component::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('components.index')) + ->post(route('components.checkout.store', $component), [ + 'asset_id' => Asset::factory()->create()->id, + 'redirect_option' => 'index', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('components.index')); + } + + public function testComponentCheckoutPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $component = Component::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('components.index')) + ->post(route('components.checkout.store' , $component), [ + 'asset_id' => Asset::factory()->create()->id, + 'redirect_option' => 'item', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('components.show', ['component' => $component->id])); + } + + public function testComponentCheckoutPagePostIsRedirectedIfRedirectSelectionIsTarget() + { + $asset = Asset::factory()->create(); + $component = Component::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('components.index')) + ->post(route('components.checkout.store' , $component), [ + 'asset_id' => $asset->id, + 'redirect_option' => 'target', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('hardware.show', ['hardware' => $asset])); + } + + +} diff --git a/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php b/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php index b595640493..90132fcedf 100644 --- a/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php @@ -3,6 +3,8 @@ namespace Tests\Feature\Checkouts\Ui; use App\Models\Actionlog; +use App\Models\Asset; +use App\Models\Component; use App\Models\Consumable; use App\Models\User; use App\Notifications\CheckoutConsumableNotification; @@ -90,4 +92,51 @@ class ConsumableCheckoutTest extends TestCase 'Log entry either does not exist or there are more than expected' ); } + + public function testConsumableCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex() + { + $consumable = Consumable::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('consumables.index')) + ->post(route('consumables.checkout.store', $consumable), [ + 'assigned_to' => User::factory()->create()->id, + 'redirect_option' => 'index', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('consumables.index')); + } + + public function testConsumableCheckoutPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $consumable = Consumable::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('consumables.index')) + ->post(route('consumables.checkout.store' , $consumable), [ + 'assigned_to' => User::factory()->create()->id, + 'redirect_option' => 'item', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('consumables.show', ['consumable' => $consumable->id])); + } + + public function testConsumableCheckoutPagePostIsRedirectedIfRedirectSelectionIsTarget() + { + $user = User::factory()->create(); + $consumable = Consumable::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('components.index')) + ->post(route('consumables.checkout.store' , $consumable), [ + 'assigned_to' => $user->id, + 'redirect_option' => 'target', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('users.show', ['user' => $user])); + } + } diff --git a/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php b/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php index 8e5cd83061..9511c3ae33 100644 --- a/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php @@ -56,4 +56,62 @@ class LicenseCheckoutTest extends TestCase 'note' => 'oh hi there', ]); } + + public function testLicenseCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex() + { + $license = License::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('licenses.checkout', ['licenseId' => $license->id])) + ->post(route('licenses.checkout', ['licenseId' => $license->id]), [ + 'assigned_to' => User::factory()->create()->id, + 'redirect_option' => 'index', + 'assigned_qty' => 1, + ]) + ->assertStatus(302) + ->assertRedirect(route('licenses.index')); + } + + public function testLicenseCheckoutPagePostIsRedirectedIfRedirectSelectionIsItem() + { + $license = License::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('licenses.checkout', ['licenseId' => $license->id])) + ->post(route('licenses.checkout' , ['licenseId' => $license->id]), [ + 'assigned_to' => User::factory()->create()->id, + 'redirect_option' => 'item', + ]) + ->assertStatus(302) + ->assertRedirect(route('licenses.show', ['license' => $license->id])); + } + + public function testLicenseCheckoutPagePostIsRedirectedIfRedirectSelectionIsUserTarget() + { + $user = User::factory()->create(); + $license = License::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('licenses.checkout', ['licenseId' => $license->id])) + ->post(route('licenses.checkout' , $license), [ + 'assigned_to' => $user->id, + 'redirect_option' => 'target', + ]) + ->assertStatus(302) + ->assertRedirect(route('users.show', ['user' => $user->id])); + } + public function testLicenseCheckoutPagePostIsRedirectedIfRedirectSelectionIsAssetTarget() + { + $asset = Asset::factory()->create(); + $license = License::factory()->create(); + + $this->actingAs(User::factory()->admin()->create()) + ->from(route('licenses.checkout', ['licenseId' => $license->id])) + ->post(route('licenses.checkout' , $license), [ + 'asset_id' => $asset->id, + 'redirect_option' => 'target', + ]) + ->assertStatus(302) + ->assertRedirect(route('hardware.show', ['hardware' => $asset->id])); + } } diff --git a/tests/Feature/Licenses/Ui/CreateLicenseTest.php b/tests/Feature/Licenses/Ui/CreateLicenseTest.php new file mode 100644 index 0000000000..f24c3bd2ce --- /dev/null +++ b/tests/Feature/Licenses/Ui/CreateLicenseTest.php @@ -0,0 +1,42 @@ +create(); + $this->actingAs(User::factory()->create()) + ->get(route('licenses.create', $license)) + ->assertForbidden(); + } + + + + public function testLicenseWithoutPurchaseDateFailsValidation() + { + $response = $this->actingAs(User::factory()->superuser()->create()) + ->from(route('licenses.create')) + ->post(route('licenses.store'), [ + 'name' => 'Test Invalid License', + 'seats' => '10', + 'category_id' => Category::factory()->forLicenses()->create()->id, + 'depreciation_id' => Depreciation::factory()->create()->id + ]); + $response->assertStatus(302); + $response->assertRedirect(route('licenses.create')); + $response->assertInvalid(['purchase_date']); + $response->assertSessionHasErrors(['purchase_date']); + $this->followRedirects($response)->assertSee(trans('general.error')); + $this->assertFalse(AssetModel::where('name', 'Test Invalid License')->exists()); + + } +} diff --git a/tests/Feature/Licenses/Ui/LicenseViewTest.php b/tests/Feature/Licenses/Ui/LicenseViewTest.php new file mode 100644 index 0000000000..3b1f7830d4 --- /dev/null +++ b/tests/Feature/Licenses/Ui/LicenseViewTest.php @@ -0,0 +1,31 @@ +create(); + $this->actingAs(User::factory()->create()) + ->get(route('licenses.show', $license)) + ->assertForbidden(); + } + + public function testLicenseWithPurchaseDateDepreciatesCorrectly() + { + $depreciation = Depreciation::factory()->create(['months' => 12]); + $license = License::factory()->create(['depreciation_id' => $depreciation->id, 'purchase_date' => '2020-01-01']); + $this->actingAs(User::factory()->superuser()->create()) + ->get(route('licenses.show', $license)) + ->assertOk() + ->assertSee([ + '2021-01-01' + ], false); + } +} diff --git a/tests/Feature/Locations/Api/LocationsViewTest.php b/tests/Feature/Locations/Api/LocationsViewTest.php new file mode 100644 index 0000000000..1d57e58263 --- /dev/null +++ b/tests/Feature/Locations/Api/LocationsViewTest.php @@ -0,0 +1,44 @@ +create(); + $this->actingAsForApi(User::factory()->create()) + ->getJson(route('api.locations.show', $location->id)) + ->assertForbidden(); + } + + public function testViewingLocationAssetIndexRequiresPermission() + { + $location = Location::factory()->create(); + $this->actingAsForApi(User::factory()->create()) + ->getJson(route('api.locations.viewassets', $location->id)) + ->assertForbidden(); + } + + public function testViewingLocationAssetIndex() + { + $location = Location::factory()->create(); + Asset::factory()->count(3)->assignedToLocation($location)->create(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson(route('api.locations.viewassets', $location->id)) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson([ + 'total' => 3, + ]); + } +} diff --git a/tests/Unit/Helpers/HelperTest.php b/tests/Unit/Helpers/HelperTest.php index 3ef114b798..2fb1c58e26 100644 --- a/tests/Unit/Helpers/HelperTest.php +++ b/tests/Unit/Helpers/HelperTest.php @@ -31,46 +31,135 @@ class HelperTest extends TestCase public function testGetRedirectOptionMethod() { $test_data = [ - 'Option 2: redirect for user assigned to ' => [ + 'Option target: redirect for user assigned to ' => [ 'request' =>(object) ['assigned_user' => 22], 'id' => 1, 'checkout_to_type' => 'user', - 'redirect_option' => 2, + 'redirect_option' => 'target', 'table' => 'Assets', 'route' => route('users.show', 22), ], - 'Option 2: redirect location assigned to ' => [ + 'Option target: redirect location assigned to ' => [ 'request' =>(object) ['assigned_location' => 10], 'id' => 2, 'checkout_to_type' => 'location', - 'redirect_option' => 2, + 'redirect_option' => 'target', 'table' => 'Locations', 'route' => route('locations.show', 10), ], - 'Option 2: redirect back to asset assigned to ' => [ + 'Option target: redirect back to asset assigned to ' => [ 'request' =>(object) ['assigned_asset' => 101], 'id' => 3, 'checkout_to_type' => 'asset', - 'redirect_option' => 2, + 'redirect_option' => 'target', 'table' => 'Assets', 'route' => route('hardware.show', 101), ], - 'Option 1: redirect back to asset ' => [ + 'Option item: redirect back to asset ' => [ 'request' =>(object) ['assigned_asset' => null], 'id' => 999, 'checkout_to_type' => null, - 'redirect_option' => 1, + 'redirect_option' => 'item', 'table' => 'Assets', 'route' => route('hardware.show', 999), ], - 'Option 0: redirect back to index ' => [ + 'Option index: redirect back to asset index ' => [ 'request' =>(object) ['assigned_asset' => null], 'id' => null, 'checkout_to_type' => null, - 'redirect_option' => 0, + 'redirect_option' => 'index', 'table' => 'Assets', 'route' => route('hardware.index'), ], + + 'Option item: redirect back to user ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => 999, + 'checkout_to_type' => null, + 'redirect_option' => 'item', + 'table' => 'Users', + 'route' => route('users.show', 999), + ], + + 'Option index: redirect back to user index ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => null, + 'checkout_to_type' => null, + 'redirect_option' => 'index', + 'table' => 'Users', + 'route' => route('users.index'), + ], + + 'Option item: redirect back to license ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => 999, + 'checkout_to_type' => null, + 'redirect_option' => 'item', + 'table' => 'Licenses', + 'route' => route('licenses.show', 999), + ], + + 'Option index: redirect back to license index ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => null, + 'checkout_to_type' => null, + 'redirect_option' => 'index', + 'table' => 'Licenses', + 'route' => route('licenses.index'), + ], + + 'Option item: redirect back to accessory list ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => 999, + 'checkout_to_type' => null, + 'redirect_option' => 'item', + 'table' => 'Accessories', + 'route' => route('accessories.show', 999), + ], + + 'Option index: redirect back to accessory index ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => null, + 'checkout_to_type' => null, + 'redirect_option' => 'index', + 'table' => 'Accessories', + 'route' => route('accessories.index'), + ], + 'Option item: redirect back to consumable ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => 999, + 'checkout_to_type' => null, + 'redirect_option' => 'item', + 'table' => 'Consumables', + 'route' => route('consumables.show', 999), + ], + + 'Option index: redirect back to consumables index ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => null, + 'checkout_to_type' => null, + 'redirect_option' => 'index', + 'table' => 'Consumables', + 'route' => route('consumables.index'), + ], + + 'Option item: redirect back to component ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => 999, + 'checkout_to_type' => null, + 'redirect_option' => 'item', + 'table' => 'Components', + 'route' => route('components.show', 999), + ], + + 'Option index: redirect back to component index ' => [ + 'request' =>(object) ['assigned_asset' => null], + 'id' => null, + 'checkout_to_type' => null, + 'redirect_option' => 'index', + 'table' => 'Components', + 'route' => route('components.index'), + ], ]; foreach ($test_data as $scenario => $data ) { @@ -78,7 +167,7 @@ class HelperTest extends TestCase Session::put('redirect_option', $data['redirect_option']); Session::put('checkout_to_type', $data['checkout_to_type']); - $redirect = Helper::getRedirectOption($data['request'],$data['id'], $data['table']); + $redirect = redirect()->to(Helper::getRedirectOption($data['request'],$data['id'], $data['table'])); $this->assertInstanceOf(RedirectResponse::class, $redirect); $this->assertEquals($data['route'], $redirect->getTargetUrl(), $scenario.'failed.'); diff --git a/webpack.mix.js b/webpack.mix.js index cb5ac6eaef..fee66acfb2 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -1,4 +1,5 @@ const mix = require("laravel-mix"); +const fs = require("node:fs"); // This generates a file called app.css, which we use // later on to build all.css @@ -68,67 +69,30 @@ mix "./public/js/build/app.js" //because of compiling - this does not work very well :( ) +var skins = fs.readdirSync("resources/assets/less/skins"); + // Convert the skins to CSS -mix.less( - "./resources/assets/less/skins/skin-blue.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-red.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-contrast.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-green.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-green-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-black.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-black-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-red-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-purple.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-purple-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-yellow.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-yellow-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-blue-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-orange-dark.less", - "css/dist/skins", -); -mix.less( - "./resources/assets/less/skins/skin-orange.less", - "css/dist/skins", -); +for (var i in skins) { + mix.less( + "resources/assets/less/skins/" + skins[i], + "css/dist/skins" + ) +} + +var css_skins = fs.readdirSync("public/css/dist/skins"); +for (var i in css_skins) { + if (css_skins[i].endsWith(".min.css")) { + //don't minify already minified skinns + continue; + } + if (css_skins[i].endsWith(".css")) { + // only minify files ending with '.css' + mix.minify("public/css/dist/skins/" + css_skins[i]).version(); + } + //TODO - if we only ever use the minified versions, this could be simplified down to one line (above) + // but it stays like this so we have the minified and non-minified versions of the skins + // right now the code seems to use the un-minified skins +} /** * Combine bootstrap table css @@ -163,7 +127,7 @@ mix.combine( "./node_modules/ekko-lightbox/dist/ekko-lightbox.js", "./resources/assets/js/extensions/pGenerator.jquery.js", "./node_modules/chart.js/dist/Chart.js", - "./resources/assets/js/signature_pad.js", + "./resources/assets/js/signature_pad.js", //dupe? "./node_modules/jquery-validation/dist/jquery.validate.js", "./node_modules/list.js/dist/list.js", "./node_modules/clipboard/dist/clipboard.js", @@ -200,27 +164,4 @@ mix ["./public/js/build/vendor.js", "./public/js/build/app.js"], "./public/js/dist/all.js" ) - .version(); - -/** - * Copy, minify and version skins - */ -mix - .minify([ - "./public/css/dist/skins/skin-green.css", - "./public/css/dist/skins/skin-green-dark.css", - "./public/css/dist/skins/skin-black.css", - "./public/css/dist/skins/skin-black-dark.css", - "./public/css/dist/skins/skin-blue.css", - "./public/css/dist/skins/skin-blue-dark.css", - "./public/css/dist/skins/skin-yellow.css", - "./public/css/dist/skins/skin-yellow-dark.css", - "./public/css/dist/skins/skin-red.css", - "./public/css/dist/skins/skin-red-dark.css", - "./public/css/dist/skins/skin-purple.css", - "./public/css/dist/skins/skin-purple-dark.css", - "./public/css/dist/skins/skin-orange.css", - "./public/css/dist/skins/skin-orange-dark.css", - "./public/css/dist/skins/skin-contrast.css", - ]) - .version(); + .version(); \ No newline at end of file