From b18baf74d2d678cac6247aac1dd7c5f5f75c0bc2 Mon Sep 17 00:00:00 2001 From: arne-kroeger Date: Mon, 29 Jul 2024 10:54:53 +0200 Subject: [PATCH 1/4] added options to checkout accessoires to locations and assets Added #14979: add checkout functionality to accessoires --- app/Console/Commands/RestoreFromBackup.php | 2 +- app/Helpers/Helper.php | 4 +- .../Accessories/AccessoriesController.php | 6 +- .../AccessoryCheckinController.php | 17 +- .../AccessoryCheckoutController.php | 29 ++-- .../Controllers/Api/AccessoriesController.php | 54 +++---- .../Controllers/Users/BulkUsersController.php | 2 +- .../Requests/AccessoryCheckoutRequest.php | 11 +- .../Transformers/AccessoriesTransformer.php | 43 +++-- .../Transformers/ActionlogsTransformer.php | 4 +- app/Models/Accessory.php | 19 ++- app/Models/AccessoryCheckout.php | 148 ++++++++++++++++++ app/Models/User.php | 2 +- app/Presenters/AccessoryPresenter.php | 2 +- database/factories/AccessoryFactory.php | 9 +- ..._checkout_for_all_types_to_accessories.php | 39 +++++ .../views/accessories/checkout.blade.php | 9 +- resources/views/accessories/view.blade.php | 14 +- .../Checkins/Ui/AccessoryCheckinTest.php | 8 +- .../Checkouts/Api/AccessoryCheckoutTest.php | 26 +-- .../Checkouts/Ui/AccessoryCheckoutTest.php | 19 ++- 21 files changed, 347 insertions(+), 120 deletions(-) create mode 100755 app/Models/AccessoryCheckout.php create mode 100644 database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index 3ea696f36e..846c2933c5 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -92,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) { diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index fcb439f819..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); diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index 722638ad8f..4fd5a4c547 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -144,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()) { @@ -233,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 7a228e50ad..e36f8a240d 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php @@ -6,6 +6,7 @@ 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; @@ -24,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')); } @@ -39,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); @@ -59,10 +60,8 @@ 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); - - event(new CheckoutableCheckedIn($accessory, User::find($return_to), auth()->user(), $request->input('note'), $checkin_at)); + if ($accessory_checkout->delete()) { + event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at)); session()->put(['redirect_option' => $request->get('redirect_option')]); diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php index 5b10e99bce..03fb6ac250 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php @@ -4,9 +4,11 @@ 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; @@ -16,6 +18,9 @@ use \Illuminate\Http\RedirectResponse; class AccessoryCheckoutController extends Controller { + + use CheckInOutRequest; + /** * Return the form to checkout an Accessory to a user. * @@ -25,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); @@ -58,30 +63,32 @@ 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' => 'user']); - $request->request->add(['assigned_user' => $user->id]); + $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')]); diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index 1ffdcaf193..12f3f0e54e 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 = DB::table('accessories_checkout')->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 (DB::table('accessories_checkout')->where('id', '=', $accessory_checkout->id)->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/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index b0683e2cbc..fcd295ca61 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -219,7 +219,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(); - $accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get(); + $accessories = DB::table('accessories_checkout')->whereIn('assigned_to', $user_raw_array)->get(); $licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get(); $consumables = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get(); 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/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/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/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/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/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..41c4e506fc --- /dev/null +++ b/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php @@ -0,0 +1,39 @@ +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::rename('accessories_checkout', 'accessories_users'); + } + } +}; diff --git a/resources/views/accessories/checkout.blade.php b/resources/views/accessories/checkout.blade.php index 6c96e1bfa3..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'])
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)
{{ trans('general.user') }}{{ trans('general.checked_out_to') }} {{ trans('general.notes') }} {{ trans('admin/hardware/table.checkout_date') }} {{ trans('table.actions') }}