diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index a46e841302..96ec01354d 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -95,10 +95,6 @@ class AssetsController extends Controller 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); - - - - // These are used by the API to query against specific ID numbers. // They are also used by the individual searches on detail pages like // locations, etc. @@ -106,6 +102,10 @@ class AssetsController extends Controller $assets->where('assets.status_id', '=', $request->input('status_id')); } + if ($request->input('requestable')=='true') { + $assets->where('assets.requestable', '=', '1'); + } + if ($request->has('model_id')) { $assets->InModelList([$request->input('model_id')]); } @@ -736,5 +736,50 @@ class AssetsController extends Controller + } + + + + /** + * Returns JSON listing of all requestable assets + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return JsonResponse + */ + public function requestable(Request $request) + { + + $assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets") + ->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo', + 'model.category', 'model.manufacturer', 'model.fieldset','supplier')->where('assets.requestable', '=', '1'); + + $offset = request('offset', 0); + $limit = $request->input('limit', 50); + $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; + $assets->TextSearch($request->input('search')); + + switch ($request->input('sort')) { + case 'model': + $assets->OrderModels($order); + break; + case 'model_number': + $assets->OrderModelNumber($order); + break; + case 'category': + $assets->OrderCategory($order); + break; + case 'manufacturer': + $assets->OrderManufacturer($order); + break; + default: + $assets->orderBy('assets.created_at', $order); + break; + } + + + $total = $assets->count(); + $assets = $assets->skip($offset)->take($limit)->get(); + return (new AssetsTransformer)->transformRequestedAssets($assets, $total); } } diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php new file mode 100644 index 0000000000..378945f855 --- /dev/null +++ b/app/Http/Controllers/Api/ProfileController.php @@ -0,0 +1,44 @@ +] + * @since [v4.3.0] + * + * @return Array + */ + public function requestedAssets() + { + $checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get(); + + $results = []; + $results['total'] = $checkoutRequests->count(); + + + foreach ($checkoutRequests as $checkoutRequest) { + $results['rows'][] = [ + 'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(), + 'name' => $checkoutRequest->itemRequested()->present()->name(), + 'type' => $checkoutRequest->itemType(), + 'qty' => $checkoutRequest->quantity, + 'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null, + 'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'), + 'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'), + ]; + } + return $results; + } + + +} diff --git a/app/Http/Controllers/Api/SettingsController.php b/app/Http/Controllers/Api/SettingsController.php index 0d44337e5d..2a4b757674 100644 --- a/app/Http/Controllers/Api/SettingsController.php +++ b/app/Http/Controllers/Api/SettingsController.php @@ -134,12 +134,6 @@ class SettingsController extends Controller if (!config('app.lock_passwords')) { try { Notification::send(Setting::first(), new MailTest()); - - /*Mail::send('emails.test', [], function ($m) { - $m->to(config('mail.reply_to.address'), config('mail.reply_to.name')); - $m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name')); - $m->subject(trans('mail.test_email')); - });*/ return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200); } catch (Exception $e) { return response()->json(['message' => $e->getMessage()], 500); diff --git a/app/Http/Controllers/AssetsController.php b/app/Http/Controllers/AssetsController.php index 7a5dcf1a4d..280a841e96 100755 --- a/app/Http/Controllers/AssetsController.php +++ b/app/Http/Controllers/AssetsController.php @@ -1305,12 +1305,16 @@ class AssetsController extends Controller } } - public function getRequestedIndex($id = null) + public function getRequestedIndex($user_id = null) { - if ($id) { - $requestedItems = CheckoutRequest::where('user_id', $id)->with('user', 'requestedItem')->get(); + $requestedItems = CheckoutRequest::with('user', 'requestedItem')->whereNull('canceled_at')->with('user', 'requestedItem'); + + if ($user_id) { + $requestedItems->where('user_id', $user_id)->get(); } - $requestedItems = CheckoutRequest::with('user', 'requestedItem')->get(); + + $requestedItems = $requestedItems->orderBy('created_at', 'desc')->get(); + return view('hardware/requested', compact('requestedItems')); } diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php index 96f893042e..b0f0dae15f 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -12,6 +12,8 @@ use App\Models\Consumable; use App\Models\License; use App\Models\Setting; use App\Models\User; +use App\Notifications\RequestAssetNotification; +use App\Notifications\RequestAssetCancelationNotification; use Auth; use Config; use DB; @@ -80,116 +82,71 @@ class ViewAssetsController extends Controller { $item = null; $fullItemType = 'App\\Models\\' . studly_case($itemType); + if ($itemType == "asset_model") { $itemType = "model"; } $item = call_user_func(array($fullItemType, 'find'), $itemId); + $user = Auth::user(); - $quantity = $data['item_quantity'] = Input::has('request-quantity') ? e(Input::get('request-quantity')) : 1; + $logaction = new Actionlog(); $logaction->item_id = $data['asset_id'] = $item->id; $logaction->item_type = $fullItemType; $logaction->created_at = $data['requested_date'] = date("Y-m-d H:i:s"); + if ($user->location_id) { $logaction->location_id = $user->location_id; } $logaction->target_id = $data['user_id'] = Auth::user()->id; $logaction->target_type = User::class; + $data['item_quantity'] = Input::has('request-quantity') ? e(Input::get('request-quantity')) : 1; $data['requested_by'] = $user->present()->fullName(); - $data['item_name'] = $item->name; + $data['item'] = $item; $data['item_type'] = $itemType; + $data['target'] = Auth::user(); + if ($fullItemType == Asset::class) { $data['item_url'] = route('hardware.show', $item->id); - $slackMessage = ' Asset <'.url('/').'/hardware/'.$item->id.'/view'.'|'.$item->present()->name().'> requested by <'.url('/').'/users/'.$item->user_id.'/view'.'|'.$user->present()->fullName().'>.'; } else { $data['item_url'] = route("view/${itemType}", $item->id); - $slackMessage = $quantity. ' ' . class_basename(strtoupper($logaction->item_type)).' <'.$data['item_url'].'|'.$item->name.'> requested by <'.url('/').'/user/'.$item->id.'/view'.'|'.$user->present()->fullName().'>.'; + } $settings = Setting::getSettings(); - if ($settings->slack_endpoint) { - - $slack_settings = [ - 'username' => $settings->botname, - 'channel' => $settings->slack_channel, - 'link_names' => true - ]; - - $slackClient = new \Maknz\Slack\Client($settings->slack_endpoint, $slack_settings); - } - - if ($item->isRequestedBy($user)) { - - $item->cancelRequest(); - $log = $logaction->logaction('request_canceled'); + if ($item_request = $item->isRequestedBy($user)) { + $item->cancelRequest(); + $data['item_quantity'] = $item_request->qty; + $logaction->logaction('request_canceled'); if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) { - Mail::send('emails.asset-canceled', $data, function ($m) use ($user, $settings) { - $m->to(explode(',', $settings->alert_email), $settings->site_name); - $m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name')); - $m->subject(trans('mail.Item_Request_Canceled')); - }); - } - - if ($settings->slack_endpoint) { - try { - $slackClient->attach([ - 'color' => 'good', - 'fields' => [ - [ - 'title' => 'CANCELED:', - 'value' => $slackMessage - ] - - ] - ])->send('Item Request Canceled'); - - } catch (Exception $e) { - - } + $settings->notify(new RequestAssetCancelationNotification($data)); } return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled')); } else { $item->request(); - - $log = $logaction->logaction('requested'); - - if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) { - Mail::send('emails.asset-requested', $data, function ($m) use ($user, $settings) { - $m->to(explode(',', $settings->alert_email), $settings->site_name); - $m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name')); - $m->subject(trans('mail.Item_Requested')); - }); + $logaction->logaction('requested'); + $settings->notify(new RequestAssetNotification($data)); } - if ($settings->slack_endpoint) { - try { - $slackClient->attach([ - 'color' => 'good', - 'fields' => [ - [ - 'title' => 'REQUESTED:', - 'value' => $slackMessage - ] - ] - ])->send('Item Requested'); - - } catch (Exception $e) { - - } - } return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); } } + + + + + + public function getRequestAsset($assetId = null) { @@ -197,74 +154,45 @@ class ViewAssetsController extends Controller // Check if the asset exists and is requestable if (is_null($asset = Asset::RequestableAssets()->find($assetId))) { - // Redirect to the asset management page - return redirect()->route('requestable-assets')->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable')); + return redirect()->route('requestable-assets') + ->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable')); } elseif (!Company::isCurrentUserHasAccess($asset)) { - return redirect()->route('requestable-assets')->with('error', trans('general.insufficient_permissions')); + return redirect()->route('requestable-assets') + ->with('error', trans('general.insufficient_permissions')); } - // If it's requested, cancel the request. + + $data['item'] = $asset; + $data['target'] = Auth::user(); + $data['item_quantity'] = 1; + $settings = Setting::getSettings(); + + $logaction = new Actionlog(); + $logaction->item_id = $data['asset_id'] = $asset->id; + $logaction->item_type = $data['item_type'] = Asset::class; + $logaction->created_at = $data['requested_date'] = date("Y-m-d H:i:s"); + + if ($user->location_id) { + $logaction->location_id = $user->location_id; + } + $logaction->target_id = $data['user_id'] = Auth::user()->id; + $logaction->target_type = User::class; + + + // If it's already requested, cancel the request. if ($asset->isRequestedBy(Auth::user())) { $asset->cancelRequest(); - return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); + $logaction->logaction('request canceled'); + $settings->notify(new RequestAssetCancelationNotification($data)); + return redirect()->route('requestable-assets') + ->with('success')->with('success', trans('admin/hardware/message.requests.cancel-success')); } else { - $logaction = new Actionlog(); - $logaction->item_id = $data['asset_id'] = $asset->id; - $logaction->item_type = Asset::class; - $logaction->created_at = $data['requested_date'] = date("Y-m-d H:i:s"); - $data['asset_type'] = 'hardware'; - if ($user->location_id) { - $logaction->location_id = $user->location_id; - } - $logaction->target_id = $data['user_id'] = Auth::user()->id; - $logaction->target_type = User::class; - $log = $logaction->logaction('requested'); - - $data['requested_by'] = $user->present()->fullName(); - $data['asset_name'] = $asset->present()->name(); - - $settings = Setting::getSettings(); - - if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) { - Mail::send('emails.asset-requested', $data, function ($m) use ($user, $settings) { - $m->to(explode(',', $settings->alert_email), $settings->site_name); - $m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name')); - $m->subject(trans('mail.asset_requested')); - }); - } + $logaction->logaction('requested'); $asset->request(); + $settings->notify(new RequestAssetNotification($data)); - if ($settings->slack_endpoint) { - - - $slack_settings = [ - 'username' => $settings->botname, - 'channel' => $settings->slack_channel, - 'link_names' => true - ]; - - $client = new \Maknz\Slack\Client($settings->slack_endpoint, $slack_settings); - - try { - $client->attach([ - 'color' => 'good', - 'fields' => [ - [ - 'title' => 'REQUESTED:', - 'value' => class_basename(strtoupper($logaction->item_type)).' asset <'.url('/').'/hardware/'.$asset->id.'/view'.'|'.$asset->present()->name().'> requested by <'.url('/').'/hardware/'.$asset->id.'/view'.'|'.Auth::user()->present()->fullName().'>.' - ] - - ] - ])->send('Asset Requested'); - - } catch (Exception $e) { - - } - - } - return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); } @@ -273,13 +201,10 @@ class ViewAssetsController extends Controller public function getRequestedAssets() { - $checkoutrequests = CheckoutRequest::all(); - - return view('account/requested-items', compact($checkoutrequests)); + return view('account/requested'); } - // Get the acceptance screen public function getAcceptAsset($logID = null) { diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php index 3877f7bc2e..3c1dd3bcd0 100644 --- a/app/Http/Transformers/AssetsTransformer.php +++ b/app/Http/Transformers/AssetsTransformer.php @@ -160,4 +160,39 @@ class AssetsTransformer 'type' => $asset->assignedType() ] : null; } + + + public function transformRequestedAssets(Collection $assets, $total) + { + $array = array(); + foreach ($assets as $asset) { + $array[] = self::transformRequestedAsset($asset); + } + return (new DatatablesTransformer)->transformDatatables($array, $total); + } + + public function transformRequestedAsset(Asset $asset) { + $array = [ + 'id' => (int) $asset->id, + 'name' => e($asset->name), + 'asset_tag' => e($asset->asset_tag), + 'serial' => e($asset->serial), + 'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null, + 'model' => ($asset->model) ? e($asset->model->name) : null, + 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, + 'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'), + 'location' => ($asset->location) ? e($asset->location->name) : null, + 'status'=> ($asset->assetstatus) ? $asset->present()->statusMeta : null, + ]; + + $permissions_array['available_actions'] = [ + 'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false, + 'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true, + + ]; + + $array += $permissions_array; + return $array; + + } } diff --git a/app/Models/AssetMaintenance.php b/app/Models/AssetMaintenance.php index 8a07ec0dbf..a99fa4ebd6 100644 --- a/app/Models/AssetMaintenance.php +++ b/app/Models/AssetMaintenance.php @@ -52,7 +52,8 @@ class AssetMaintenance extends Model implements ICompanyableChild return [ trans('admin/asset_maintenances/general.maintenance') => trans('admin/asset_maintenances/general.maintenance'), trans('admin/asset_maintenances/general.repair') => trans('admin/asset_maintenances/general.repair'), - trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade') + trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'), + 'PAT test' => 'PAT test', ]; } diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index 1dec88a5b0..48c86686c9 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -98,6 +98,14 @@ class AssetModel extends SnipeModel return $this->belongsTo('\App\Models\CustomFieldset', 'fieldset_id'); } + + public function getImageUrl() { + if ($this->image) { + return url('/').'/uploads/models/'.$this->image; + } + return false; + } + /** * ----------------------------------------------- * BEGIN QUERY SCOPES diff --git a/app/Models/CheckoutRequest.php b/app/Models/CheckoutRequest.php index 668f326402..df40618317 100644 --- a/app/Models/CheckoutRequest.php +++ b/app/Models/CheckoutRequest.php @@ -1,12 +1,13 @@ requests->where('user_id', $user->id); - - return $requests->count() > 0; + return $this->requests->where('canceled_at', NULL)->where('user_id', $user->id)->first(); } public function scopeRequestedBy($query, User $user) @@ -31,15 +29,20 @@ trait Requestable }); } - public function request() + public function request($qty = 1) { $this->requests()->save( - new CheckoutRequest(['user_id' => Auth::id()]) + new CheckoutRequest(['user_id' => Auth::id(), 'qty' => $qty]) ); } + public function deleteRequest() + { + $this->requests()->where('user_id', Auth::id())->delete(); + } + public function cancelRequest() { - $this->requests()->where('user_id', Auth::id())->delete(); + $this->requests()->where('user_id', Auth::id())->update(['canceled_at' => \Carbon\Carbon::now()]); } } diff --git a/app/Models/User.php b/app/Models/User.php index 4c014f9cea..8d0904ad53 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -292,7 +292,7 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo */ public function checkoutRequests() { - return $this->belongsToMany(Asset::class, 'checkout_requests'); + return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at'); } public function throttle() diff --git a/app/Notifications/RequestAssetCancelation.php b/app/Notifications/RequestAssetCancelation.php new file mode 100644 index 0000000000..3902f9af3b --- /dev/null +++ b/app/Notifications/RequestAssetCancelation.php @@ -0,0 +1,137 @@ +target = $params['target']; + $this->item = $params['item']; + $this->note = ''; + $this->last_checkout = ''; + $this->item_quantity = $params['item_quantity']; + $this->expected_checkin = ''; + $this->requested_date = \App\Helpers\Helper::getFormattedDateObject($params['requested_date'], 'datetime', + false); + $this->settings = Setting::getSettings(); + + if (array_key_exists('note', $params)) { + $this->note = $params['note']; + } + + if ($this->item->last_checkout) { + $this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date', + false); + } + + if ($this->item->expected_checkin) { + $this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date', + false); + } + + + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via() + { + + $notifyBy = []; + + if (Setting::getSettings()->slack_endpoint!='') { + \Log::debug('use slack'); + $notifyBy[] = 'slack'; + } + + + $notifyBy[] = 'mail'; + + return $notifyBy; + } + + public function toSlack() + { + + + $target = $this->target; + $item = $this->item; + $note = $this->note; + $qty = $this->item_quantity; + $botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ; + + $fields = [ + 'QTY' => $qty, + 'Canceled By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + ]; + + if (($this->expected_checkin) && ($this->expected_checkin!='')) { + $fields['Expected Checkin'] = $this->expected_checkin; + } + + return (new SlackMessage) + ->content( trans('mail.a_user_canceled')) + ->from($botname) + ->attachment(function ($attachment) use ($item, $note, $fields) { + $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + ->fields($fields) + ->content($note); + }); + } + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail() + { + + $fields = []; + + // Check if the item has custom fields associated with it + if (($this->item->model) && ($this->item->model->fieldset)) { + $fields = $this->item->model->fieldset->fields; + } + + $message = (new MailMessage)->markdown('notifications.markdown.asset-requested', + [ + 'item' => $this->item, + 'note' => $this->note, + 'requested_by' => $this->target, + 'requested_date' => $this->requested_date, + 'fields' => $fields, + 'qty' => $this->item_quantity, + 'last_checkout' => $this->last_checkout, + 'expected_checkin' => $this->expected_checkin, + 'intro_text' => trans('mail.a_user_canceled'), + ]) + ->subject(trans('Item Request Canceled')); + + + return $message; + + + } + +} diff --git a/app/Notifications/RequestAssetNotification.php b/app/Notifications/RequestAssetNotification.php new file mode 100644 index 0000000000..eac2cef22f --- /dev/null +++ b/app/Notifications/RequestAssetNotification.php @@ -0,0 +1,134 @@ +target = $params['target']; + $this->item = $params['item']; + $this->item_type = $params['item_type']; + $this->item_quantity = $params['item_quantity']; + $this->note = ''; + $this->last_checkout = ''; + $this->expected_checkin = ''; + $this->requested_date = \App\Helpers\Helper::getFormattedDateObject($params['requested_date'], 'datetime', + false); + $this->settings = Setting::getSettings(); + + if (array_key_exists('note', $params)) { + $this->note = $params['note']; + } + + if ($this->item->last_checkout) { + $this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date', + false); + } + + if ($this->item->expected_checkin) { + $this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date', + false); + } + + + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via() + { + + $notifyBy = []; + + if (Setting::getSettings()->slack_endpoint!='') { + \Log::debug('use slack'); + $notifyBy[] = 'slack'; + } + + + $notifyBy[] = 'mail'; + + return $notifyBy; + } + + public function toSlack() + { + + + $target = $this->target; + $qty = $this->item_quantity; + $item = $this->item; + $note = $this->note; + $botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ; + + $fields = [ + 'QTY' => $qty, + 'Requested By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + ]; + + return (new SlackMessage) + ->content(trans('mail.Item_Requested')) + ->from($botname) + ->attachment(function ($attachment) use ($item, $note, $fields) { + $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + ->fields($fields) + ->content($note); + }); + } + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail() + { + + $fields = []; + + // Check if the item has custom fields associated with it + if (($this->item->model) && ($this->item->model->fieldset)) { + $fields = $this->item->model->fieldset->fields; + } + + $message = (new MailMessage)->markdown('notifications.markdown.asset-requested', + [ + 'item' => $this->item, + 'note' => $this->note, + 'requested_by' => $this->target, + 'requested_date' => $this->requested_date, + 'fields' => $fields, + 'last_checkout' => $this->last_checkout, + 'expected_checkin' => $this->expected_checkin, + 'intro_text' => trans('mail.a_user_requested'), + 'qty' => $this->item_quantity, + ]) + ->subject(trans('mail.Item_Requested')); + + + return $message; + + + } + +} diff --git a/database/migrations/2018_03_29_053618_add_canceled_at_and_fulfilled_at_in_requests.php b/database/migrations/2018_03_29_053618_add_canceled_at_and_fulfilled_at_in_requests.php new file mode 100644 index 0000000000..dd8648c666 --- /dev/null +++ b/database/migrations/2018_03_29_053618_add_canceled_at_and_fulfilled_at_in_requests.php @@ -0,0 +1,36 @@ +dateTime('canceled_at')->nullable()->default(null); + $table->dateTime('fulfilled_at')->nullable()->default(null); + $table->dateTime('deleted_at')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('checkout_requests', function (Blueprint $table) { + $table->dropColumn('canceled_at'); + $table->dropColumn('fulfilled_at'); + $table->dropColumn('deleted_at'); + }); + } +} diff --git a/database/migrations/2018_03_29_070121_add_drop_unique_requests.php b/database/migrations/2018_03_29_070121_add_drop_unique_requests.php new file mode 100644 index 0000000000..a6299ae7a9 --- /dev/null +++ b/database/migrations/2018_03_29_070121_add_drop_unique_requests.php @@ -0,0 +1,32 @@ +dropUnique('checkout_requests_user_id_requestable_id_requestable_type_unique'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('checkout_requests', function (Blueprint $table) { + $table->index(['user_id','requestable_id','requestable_type'], 'checkout_requests_user_id_requestable_id_requestable_type_unique')->unique(); + }); + } +} diff --git a/database/migrations/2018_03_29_070511_add_new_index_requestable.php b/database/migrations/2018_03_29_070511_add_new_index_requestable.php new file mode 100644 index 0000000000..09c06566e8 --- /dev/null +++ b/database/migrations/2018_03_29_070511_add_new_index_requestable.php @@ -0,0 +1,32 @@ +index(['user_id','requestable_id','requestable_type'], 'checkout_requests_user_id_requestable_id_requestable_type'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('checkout_requests', function (Blueprint $table) { + $table->dropIndex('checkout_requests_user_id_requestable_id_requestable_type'); + }); + } +} diff --git a/resources/views/account/requestable-assets.blade.php b/resources/views/account/requestable-assets.blade.php index 3a7c77b7b5..5998acee54 100644 --- a/resources/views/account/requestable-assets.blade.php +++ b/resources/views/account/requestable-assets.blade.php @@ -29,97 +29,40 @@
- - @if ($assets->count() > 0) -
+ data-click-to-select="true" + data-cookie-id-table="requestableAssetsListingTable" + data-pagination="true" + data-id-table="requestableAssetsListingTable" + data-search="true" + data-side-pagination="server" + data-show-columns="true" + data-show-export="false" + data-show-footer="false" + data-show-refresh="true" + data-sort-order="asc" + data-sort-name="name" + data-toolbar="#toolbar" + id="assetsListingTable" + class="table table-striped snipe-table" + data-url="{{ route('api.assets.requestable', ['requestable' => true]) }}"> + - - - - - @if ($snipeSettings->display_asset_name) - - @endif - - - - - + + + + + + + + + + - - @foreach ($assets as $asset) - - - - - - - - @if ($snipeSettings->display_asset_name) - - @endif - - - - - @if ($asset->assigned_to != '' && $asset->assigned_to > 0) - - @else - - @endif - - - - - - @endforeach -
{{ trans('general.image') }}{{ trans('admin/hardware/table.asset_model') }}{{ trans('admin/models/table.modelnumber') }}{{ trans('admin/hardware/form.name') }}{{ trans('admin/hardware/table.serial') }}{{ trans('admin/hardware/table.location') }}{{ trans('admin/hardware/table.status') }}{{ trans('admin/hardware/form.expected_checkin') }}{{ trans('table.actions') }}
{{ trans('general.image') }}{{ trans('admin/hardware/table.asset_model') }}{{ trans('admin/models/table.modelnumber') }}{{ trans('admin/hardware/form.name') }}{{ trans('admin/hardware/table.serial') }}{{ trans('admin/hardware/table.location') }}{{ trans('admin/hardware/table.status') }}{{ trans('admin/hardware/form.expected_checkin') }}{{ trans('table.actions') }}
- @if ($asset->getImageUrl()) - - - - @endif - - {{ $asset->model->name }} - - - {{ $asset->model->model_number }} - {{ $asset->name }}{{ $asset->serial }} - @if ($asset->location) - {{ $asset->location->name }} - @endif - Checked out{{ trans('admin/hardware/general.requestable') }}{{ $asset->expected_checkin }} -
- {{ csrf_field() }} - @if ($asset->isRequestedBy(Auth::user())) - {{Form::submit(trans('button.cancel'), ['class' => 'btn btn-danger btn-sm'])}} - @else - {{Form::submit(trans('button.request'), ['class' => 'btn btn-primary btn-sm'])}} - @endif -
-
- - @else - -
- - {{ trans('general.no_results') }} -
- - @endif -
diff --git a/resources/views/account/requested.blade.php b/resources/views/account/requested.blade.php new file mode 100644 index 0000000000..a59d040268 --- /dev/null +++ b/resources/views/account/requested.blade.php @@ -0,0 +1,54 @@ +@extends('layouts/default') + +{{-- Page title --}} +@section('title') + Requested Assets +@stop + +{{-- Account page content --}} +@section('content') + +
+
+ +
+
+ + + + + + + + + + + + + +
ImageItem NameType{{ trans('general.qty') }}{{ trans('admin/hardware/table.location') }} {{ trans('admin/hardware/form.expected_checkin') }}Requested Date
+ +
+
+
+
+ +@stop +@section('moar_scripts') + @include ('partials.bootstrap-table') +@stop diff --git a/resources/views/account/view-assets.blade.php b/resources/views/account/view-assets.blade.php index 53c28678fc..7adab5f529 100755 --- a/resources/views/account/view-assets.blade.php +++ b/resources/views/account/view-assets.blade.php @@ -266,7 +266,7 @@ View Assets for {{ $user->present()->fullName() }} data-show-columns="true" data-show-export="true" data-show-refresh="true" - data-sort-order="asc" + data-sort-order="desc" id="userActivityReport" class="table table-striped snipe-table" data-url="{{route('api.activity.index', ['target_id' => $user->id, 'target_type' => 'User', 'order' => 'desc']) }}" diff --git a/resources/views/emails/asset-canceled.blade.php b/resources/views/emails/asset-canceled.blade.php deleted file mode 100644 index 6f434b6833..0000000000 --- a/resources/views/emails/asset-canceled.blade.php +++ /dev/null @@ -1,17 +0,0 @@ -@extends('emails/layouts/default') - -@section('content') - -

{{ trans('mail.a_user_canceled') }} {{ $snipeSettings->site_name }}.

- -

{{ trans('mail.user') }} {{ $requested_by }}
- {{ trans('mail.item') }} {{ $item_name }} ({{ $item_type }})
- {{ trans('mail.canceled') }} {{ $requested_date }} -

- -@if ($snipeSettings->show_url_in_emails=='1') -

{{ $snipeSettings->site_name }}

-@else -

{{ $snipeSettings->site_name }}

-@endif -@stop diff --git a/resources/views/emails/asset-requested.blade.php b/resources/views/emails/asset-requested.blade.php deleted file mode 100644 index e8a2a253d9..0000000000 --- a/resources/views/emails/asset-requested.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -@extends('emails/layouts/default') - -@section('content') - -

{{ trans('mail.a_user_requested') }} {{ $snipeSettings->site_name }}.

- -

{{ trans('mail.user') }} {{ $requested_by }}
- {{ trans('mail.item') }} {{ $item_name }} ({{ $item_type }})
- {{ trans('general.requested') }} {{ $requested_date }} -@if ($item_quantity > 1) -
{{ trans('general.qty') }} {{ $item_quantity}} -@endif - -@if ($snipeSettings->show_url_in_emails=='1') -

{{ $snipeSettings->site_name }}

-@else -

{{ $snipeSettings->site_name }}

-@endif - -@stop diff --git a/resources/views/hardware/requested.blade.php b/resources/views/hardware/requested.blade.php index c6c131f723..2bfff4c80c 100644 --- a/resources/views/hardware/requested.blade.php +++ b/resources/views/hardware/requested.blade.php @@ -28,13 +28,22 @@ @if ($requestedItems->count() > 0)
+ data-search="true" + data-show-columns="true" + data-show-export="true" + data-pagination="true" + data-id-table="requestedAssets" + data-cookie-id-table="requestedAssets" + data-url="{{ route('api.consumables.index') }}" + data-export-options='{ + "fileName": "export-assetrequests-{{ date('Y-m-d') }}", + "ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"] + }'> @@ -44,6 +53,7 @@ + diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index da446d9e27..07c01c6376 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -307,6 +307,16 @@ {{ trans('general.viewassets') }} + +
  • + + + Requested Assets +
  • + + + +
  • diff --git a/resources/views/notifications/markdown/asset-requested.blade.php b/resources/views/notifications/markdown/asset-requested.blade.php new file mode 100644 index 0000000000..0fe0c6298e --- /dev/null +++ b/resources/views/notifications/markdown/asset-requested.blade.php @@ -0,0 +1,62 @@ +@component('mail::message') +# {{ trans('mail.hello') }}, + +{{ $intro_text }}. + +@if (($snipeSettings->show_images_in_email =='1') && $item->getImageUrl()) +
    Asset
    +@endif + +@component('mail::table') +| | | +| ------------- | ------------- | +@if (isset($qty)) +| **{{ trans('general.qty') }}** | {{ $qty }} +@endif +| **{{ trans('mail.user') }}** | [{{ $requested_by->present()->fullName() }}]({{ route('users.show', $requested_by->id) }}) | +| **{{ trans('general.requested') }}** | {{ $requested_date }} | +@if ((isset($item->asset_tag)) && ($item->asset_tag!='')) +| **{{ trans('mail.asset_tag') }}** | {{ $item->asset_tag }} | +@endif +@if ((isset($item->name)) && ($item->name!='')) +| **{{ trans('mail.asset_name') }}** | {{ $item->name }} | +@endif +@if (isset($item->assetstatus)) +| **{{ trans('general.status') }}** | {{ $item->assetstatus->name }} +@endif +@if ($item->assignedTo) +| **Checked out to** | {!! $item->assignedTo->present()->nameUrl() !!} ({{ $item->present()->statusMeta }}) +@endif +@if (isset($item->manufacturer)) +| **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} | +@endif +@if (isset($item->model)) +| **{{ trans('general.asset_model') }}** | {{ $item->model->name }} | +@endif +@if (isset($item->model_no)) +| **{{ trans('general.model_no') }}** | {{ $item->model_no }} | +@endif +@if (isset($item->serial)) +| **{{ trans('mail.serial') }}** | {{ $item->serial }} | +@endif +@if ((isset($last_checkout)) && ($last_checkout!='')) +| **Last Checkout** | {{ $last_checkout }} | +@endif +@if ((isset($expected_checkin)) && ($expected_checkin!='')) +| **{{ trans('mail.expecting_checkin_date') }}** | {{ $expected_checkin }} | +@endif +@foreach($fields as $field) +@if (($item->{ $field->db_column_name() }!='') && ($field->show_in_email) && ($field->field_encrypted=='0')) +| **{{ $field->name }}** | {{ $item->{ $field->db_column_name() } }} | +@endif +@endforeach +@if ($note) +| **{{ trans('mail.additional_notes') }}** | {{ $note }} | +@endif +@endcomponent + +Thanks, + +{{ $snipeSettings->site_name }} + +@endcomponent diff --git a/resources/views/notifications/markdown/checkin-asset.blade.php b/resources/views/notifications/markdown/checkin-asset.blade.php index 22a39bfe64..66befd6d0a 100644 --- a/resources/views/notifications/markdown/checkin-asset.blade.php +++ b/resources/views/notifications/markdown/checkin-asset.blade.php @@ -10,7 +10,9 @@ @component('mail::table') | | | | ------------- | ------------- | +@if ((isset($item->name)) && ($item->name!='')) | **{{ trans('mail.asset_name') }}** | {{ $item->name }} | +@endif | **{{ trans('mail.asset_tag') }}** | {{ $item->asset_tag }} | @if (isset($item->manufacturer)) | **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} | diff --git a/resources/views/notifications/markdown/checkout-asset.blade.php b/resources/views/notifications/markdown/checkout-asset.blade.php index bd0b737a03..264246efab 100644 --- a/resources/views/notifications/markdown/checkout-asset.blade.php +++ b/resources/views/notifications/markdown/checkout-asset.blade.php @@ -10,7 +10,9 @@ @component('mail::table') | | | | ------------- | ------------- | +@if ((isset($item->name)) && ($item->name!='')) | **{{ trans('mail.asset_name') }}** | {{ $item->name }} | +@endif | **{{ trans('mail.asset_tag') }}** | {{ $item->asset_tag }} | @if (isset($item->manufacturer)) | **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} | diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 82fc53050a..f8f518a1b4 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -301,6 +301,17 @@ } + // This is only used by the requestable assets section + function assetRequestActionsFormatter (row, value) { + if (value.available_actions.cancel == true) { + return '
    '; + } else if (value.available_actions.request == true) { + return '
    '; + } + + } + + var formatters = [ 'hardware', diff --git a/routes/api.php b/routes/api.php index 15dfe09998..30bafae0e1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,8 +16,24 @@ use Illuminate\Http\Request; Route::group(['prefix' => 'v1','namespace' => 'Api'], function () { - /*--- Accessories API ---*/ + Route::group(['prefix' => 'account'], function () { + Route::get('requestable/hardware', + [ + 'as' => 'api.assets.requestable', + 'uses' => 'AssetsController@requestable' + ] + ); + Route::get('requests', + [ + 'as' => 'api.assets.requested', + 'uses' => 'ProfileController@requestedAssets' + ] + ); + + }); + + /*--- Accessories API ---*/ Route::resource('accessories', 'AccessoriesController', ['names' => [ @@ -716,4 +732,5 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () { ); + }); diff --git a/routes/web.php b/routes/web.php index 68c176e343..baa58ec81c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -249,6 +249,8 @@ Route::group([ 'prefix' => 'account', 'middleware' => ['auth']], function () { # View Assets Route::get('view-assets', [ 'as' => 'view-assets', 'uses' => 'ViewAssetsController@getIndex' ]); + Route::get('requested', [ 'as' => 'account.requested', 'uses' => 'ViewAssetsController@getRequestedAssets' ]); + # Accept Asset Route::get( 'accept-asset/{logID}',
  • ImageRequesting User Requested Date