] */ class AssetsController extends Controller { protected $qrCodeDimensions = array( 'height' => 3.5, 'width' => 3.5); protected $barCodeDimensions = array( 'height' => 2, 'width' => 22); public function __construct() { $this->middleware('auth'); parent::__construct(); } /** * Returns a view that invokes the ajax tables which actually contains * the content for the assets listing, which is generated in getDatatable. * * @author [A. Gianotto] [] * @see AssetController::getDatatable() method that generates the JSON response * @since [v1.0] * @param Request $request * @return View * @throws \Illuminate\Auth\Access\AuthorizationException */ public function index(Request $request) { $this->authorize('index', Asset::class); if ($request->filled('company_id')) { $company = Company::find($request->input('company_id')); } else { $company = null; } return view('hardware/index')->with('company', $company); } /** * Returns a view that presents a form to create a new asset. * * @author [A. Gianotto] [] * @since [v1.0] * @param Request $request * @return View * @internal param int $model_id */ public function create(Request $request) { $this->authorize('create', Asset::class); $view = View::make('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('item', new Asset) ->with('statuslabel_types', Helper::statusTypeList()); if ($request->filled('model_id')) { $selected_model = AssetModel::find($request->input('model_id')); $view->with('selected_model', $selected_model); } return $view; } /** * Validate and process new asset form data. * * @author [A. Gianotto] [] * @since [v1.0] * @return Redirect */ public function store(ImageUploadRequest $request) { $this->authorize(Asset::class); // Handle asset tags - there could be one, or potentially many. // This is only necessary on create, not update, since bulk editing is handled // differently $asset_tags = $request->input('asset_tags'); $success = false; $serials = $request->input('serials'); for ($a = 1; $a <= count($asset_tags); $a++) { $asset = new Asset(); $asset->model()->associate(AssetModel::find($request->input('model_id'))); $asset->name = $request->input('name'); // Check for a corresponding serial if (($serials) && (array_key_exists($a, $serials))) { $asset->serial = $serials[$a]; } $asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset->notes = $request->input('notes'); $asset->user_id = Auth::id(); $asset->archived = '0'; $asset->physical = '1'; $asset->depreciate = '0'; $asset->status_id = request('status_id', 0); $asset->warranty_months = request('warranty_months', null); $asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost')); $asset->purchase_date = request('purchase_date', null); $asset->assigned_to = request('assigned_to', null); $asset->supplier_id = request('supplier_id', 0); $asset->requestable = request('requestable', 0); $asset->rtd_location_id = request('rtd_location_id', null); if ($asset->assigned_to=='') { $asset->location_id = $request->input('rtd_location_id', null); } // Create the image (if one was chosen.) if ($request->hasFile('image')) { $image = $request->input('image'); $asset->asset_tag = $asset_tags[$a]; $asset = $request->handleImages($asset); } // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request $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')) { $asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug())); } } else { $asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug()); } } } // Validate the asset before saving if ($asset->isValid() && $asset->save()) { if (request('assigned_user')) { $target = User::find(request('assigned_user')); $location = $target->location_id; } elseif (request('assigned_asset')) { $target = Asset::find(request('assigned_asset')); $location = $target->location_id; } elseif (request('assigned_location')) { $target = Location::find(request('assigned_location')); $location = $target->id; } if (isset($target)) { $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')), $location); } $success = true; } } if ($success) { // Redirect to the asset listing page return redirect()->route('hardware.index') ->with('success', trans('admin/hardware/message.create.success')); } return redirect()->back()->withInput()->withErrors($asset->getErrors()); } /** * Returns a view that presents a form to edit an existing asset. * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return View */ public function edit($assetId = null) { if (!$item = Asset::find($assetId)) { // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } //Handles company checks and permissions. $this->authorize($item); return view('hardware/edit', compact('item')) ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()); } /** * Returns a view that presents information about an asset for detail view. * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return View */ public function show($assetId = null) { $asset = Asset::withTrashed()->find($assetId); $this->authorize('view', $asset); $settings = Setting::getSettings(); if (isset($asset)) { $audit_log = Actionlog::where('action_type', '=', 'audit') ->where('item_id', '=', $assetId) ->where('item_type', '=', Asset::class) ->orderBy('created_at', 'DESC')->first(); if ($asset->location) { $use_currency = $asset->location->currency; } else { if ($settings->default_currency!='') { $use_currency = $settings->default_currency; } else { $use_currency = trans('general.currency'); } } $qr_code = (object) array( 'display' => $settings->qr_code == '1', 'url' => route('qr_code/hardware', $asset->id) ); return view('hardware/view', compact('asset', 'qr_code', 'settings')) ->with('use_currency', $use_currency)->with('audit_log', $audit_log); } return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } /** * Validate and process asset edit form. * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return Redirect */ public function update(ImageUploadRequest $request, $assetId = null) { // Check if the asset exists if (!$asset = Asset::find($assetId)) { // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } $this->authorize($asset); $asset->status_id = $request->input('status_id', null); $asset->warranty_months = $request->input('warranty_months', null); $asset->purchase_cost = Helper::ParseFloat($request->input('purchase_cost', null)); $asset->purchase_date = $request->input('purchase_date', null); $asset->supplier_id = $request->input('supplier_id', null); // If the box isn't checked, it's not in the request at all. $asset->requestable = $request->filled('requestable'); $asset->rtd_location_id = $request->input('rtd_location_id', null); if ($asset->assigned_to=='') { $asset->location_id = $request->input('rtd_location_id', null); } if ($request->filled('image_delete')) { try { unlink(public_path().'/uploads/assets/'.$asset->image); $asset->image = ''; } catch (\Exception $e) { \Log::error($e); } } // Update the asset data $asset_tag = $request->input('asset_tags'); $serial = $request->input('serials'); $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->notes = $request->input('notes'); $asset->physical = '1'; $asset = $request->handleImages($asset); // Update custom fields in the database. // Validation for these fields is handlded through the AssetRequest form request // FIXME: No idea why this is returning a Builder error on db_column_name. // Need to investigate and fix. Using static method for now. $model = AssetModel::find($request->get('model_id')); if ($model->fieldset) { foreach ($model->fieldset->fields as $field) { if ($field->field_encrypted=='1') { if (Gate::allows('admin')) { $asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug()))); } } else { $asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug()); } } } if ($asset->save()) { return redirect()->route("hardware.show", $assetId) ->with('success', trans('admin/hardware/message.update.success')); } return redirect()->back()->withInput()->withErrors()->with('error', trans('admin/hardware/message.does_not_exist')); } /** * Delete a given asset (mark as deleted). * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return Redirect */ public function destroy($assetId) { // Check if the asset exists if (is_null($asset = Asset::find($assetId))) { // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } $this->authorize('delete', $asset); DB::table('assets') ->where('id', $asset->id) ->update(array('assigned_to' => null)); if ($asset->image) { try { Storage::disk('public')->delete('assets'.'/'.$asset->image); } catch (\Exception $e) { \Log::debug($e); } } $asset->delete(); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success')); } /** * Searches the assets table by asset tag, and redirects if it finds one * * @author [A. Gianotto] [] * @since [v3.0] * @return Redirect */ public function getAssetByTag(Request $request) { $topsearch = ($request->get('topsearch')=="true"); if (!$asset = Asset::where('asset_tag', '=', $request->get('assetTag'))->first()) { return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } $this->authorize('view', $asset); return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch); } /** * Return a QR code for the asset * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return Response */ public function getQrCode($assetId = null) { $settings = Setting::getSettings(); if ($settings->qr_code == '1') { $asset = Asset::withTrashed()->find($assetId); if ($asset) { $size = Helper::barcodeDimensions($settings->barcode_type); $qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png'; if (isset($asset->id, $asset->asset_tag)) { if (file_exists($qr_file)) { $header = ['Content-type' => 'image/png']; return response()->file($qr_file, $header); } else { $barcode = new \Com\Tecnick\Barcode\Barcode(); $barcode_obj = $barcode->getBarcodeObj($settings->barcode_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', array(-2, -2, -2, -2)); file_put_contents($qr_file, $barcode_obj->getPngData()); return response($barcode_obj->getPngData())->header('Content-type', 'image/png'); } } } return 'That asset is invalid'; } } /** * Return a 2D barcode for the asset * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return Response */ public function getBarCode($assetId = null) { $settings = Setting::getSettings(); $asset = Asset::find($assetId); $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png'; if (isset($asset->id, $asset->asset_tag)) { if (file_exists($barcode_file)) { $header = ['Content-type' => 'image/png']; return response()->file($barcode_file, $header); } else { // Calculate barcode width in pixel based on label width (inch) $barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 96.000000000001; $barcode = new \Com\Tecnick\Barcode\Barcode(); $barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode,$asset->asset_tag,($barcode_width < 300 ? $barcode_width : 300),50); file_put_contents($barcode_file, $barcode_obj->getPngData()); return response($barcode_obj->getPngData())->header('Content-type', 'image/png'); } } } /** * Returns a view that presents a form to clone an asset. * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return View */ public function getClone($assetId = null) { // Check if the asset exists if (is_null($asset_to_clone = Asset::find($assetId))) { // Redirect to the asset management page return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } $this->authorize('create', $asset_to_clone); $asset = clone $asset_to_clone; $asset->id = null; $asset->asset_tag = ''; $asset->serial = ''; $asset->assigned_to = ''; return view('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()) ->with('item', $asset); } /** * Return history import view * * @author [A. Gianotto] [] * @since [v1.0] * @return View */ public function getImportHistory() { $this->authorize('admin'); return view('hardware/history'); } /** * Import history * * This needs a LOT of love. It's done very inelegantly right now, and there are * a ton of optimizations that could (and should) be done. * * @author [herroworrd] * @since [v5.0] * @return View */ public function postImportHistory(Request $request) { if (!ini_get("auto_detect_line_endings")) { ini_set("auto_detect_line_endings", '1'); } $requiredcolumns = ['Asset Tag', 'Checkout Date', 'Checkin Date', 'Full Name']; $csv = Reader::createFromPath(Input::file('user_import_csv')); $csv->setHeaderOffset(0); //Stop here if we don't have the columns we need if(count(array_intersect($requiredcolumns, $csv->getHeader())) != count($requiredcolumns)) { $status['error'][]['csv'][]['msg'] = 'Headers do not match'; return view('hardware/history')->with('status', $status); } $statement = (new Statement()) ->orderBy(\Closure::fromCallable([$this, 'sortByName'])); $results = $statement->process($csv); $status = array(); $status['error'] = array(); $status['success'] = array(); $base_username = null; $cachetime = Carbon::now()->addSeconds(120); foreach ($results as $record) { $asset_tag = $record['Asset Tag']; try { $checkoutdate = Carbon::parse($record['Checkout Date'])->format('Y-m-d H:i:s'); $checkindate = Carbon::parse($record['Checkin Date'])->format('Y-m-d H:i:s'); } catch (\Exception $err) { $status['error'][]['asset'][$asset_tag]['msg'] = 'Your dates are screwed up. Format needs to be Y-m-d H:i:s'; continue; } if($asset = Cache::remember('asset:' . $asset_tag, $cachetime, function () use( &$asset_tag) { $tocache = Asset::where('asset_tag', '=', $asset_tag)->value('id'); return is_null($tocache) ? false : $tocache;})) { //we've found our asset, now lets look for a user if($base_username != User::generateFormattedNameFromFullName($record['Full Name'], Setting::getSettings()->username_format)) { $base_username = User::generateFormattedNameFromFullName($record['Full Name'], Setting::getSettings()->username_format); if(!$user = Cache::remember('user:' . $base_username['username'], $cachetime, function () use( &$base_username) { $tocache = User::where('username', '=', $base_username['username'])->value('id'); return is_null($tocache) ? false : $tocache;})) { $status['error'][]['asset'][$asset_tag]['msg'] = 'Asset was found but user (' . $record['Full Name'] . ') not matched'; $base_username = null; continue; } } if($checkoutdate < $checkindate) { Actionlog::firstOrCreate(array( 'item_id' => $asset, 'item_type' => Asset::class, 'user_id' => Auth::user()->id, 'note' => 'Historical record added by ' . Auth::user()->present()->fullName(), 'target_id' => $user, 'target_type' => User::class, 'created_at' => $checkoutdate, 'action_type' => 'checkout', )); Actionlog::firstOrCreate(array( 'item_id' => $asset, 'item_type' => Asset::class, 'user_id' => Auth::user()->id, 'note' => 'Historical record added by ' . Auth::user()->present()->fullName(), 'target_id' => $user, 'target_type' => User::class, 'created_at' => $checkindate, 'action_type' => 'checkin' )); $status['success'][]['asset'][$asset_tag]['msg'] = 'Asset successfully matched for ' . $record['Full Name'] . ' on ' . $checkoutdate; } else { $status['error'][]['asset'][$asset_tag]['msg'] = 'Checkin date needs to be after checkout date.'; } } else { $status['error'][]['asset'][$asset_tag]['msg'] = 'Asset not found in Snipe'; } } return view('hardware/history')->with('status', $status); } protected function sortByName(array $recordA, array $recordB): int { return strcmp($recordB['Full Name'], $recordA['Full Name']); } /** * Retore a deleted asset. * * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] * @return View */ public function getRestore($assetId = null) { // Get asset information $asset = Asset::withTrashed()->find($assetId); $this->authorize('delete', $asset); if (isset($asset->id)) { // Restore the asset Asset::withTrashed()->where('id', $assetId)->restore(); $logaction = new Actionlog(); $logaction->item_type = Asset::class; $logaction->item_id = $asset->id; $logaction->created_at = date("Y-m-d H:i:s"); $logaction->user_id = Auth::user()->id; $logaction->logaction('restored'); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success')); } return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } public function quickScan() { $this->authorize('audit', Asset::class); $dt = Carbon::now()->addMonths(12)->toDateString(); return view('hardware/quickscan')->with('next_audit_date', $dt); } public function audit($id) { $settings = Setting::getSettings(); $this->authorize('audit', Asset::class); $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); $asset = Asset::findOrFail($id); return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list'); } public function auditStore(Request $request, $id) { $this->authorize('audit', Asset::class); $rules = array( 'location_id' => 'exists:locations,id|nullable|numeric', 'next_audit_date' => 'date|nullable' ); $validator = \Validator::make($request->all(), $rules); if ($validator->fails()) { return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all())); } $asset = Asset::findOrFail($id); // We don't want to log this as a normal update, so let's bypass that $asset->unsetEventDispatcher(); $asset->next_audit_date = $request->input('next_audit_date'); $asset->last_audit_date = date('Y-m-d h:i:s'); // Check to see if they checked the box to update the physical location, // not just note it in the audit notes if ($request->input('update_location')=='1') { \Log::debug('update location in audit'); $asset->location_id = $request->input('location_id'); } if ($asset->save()) { $file_name = ''; // Upload an image, if attached if ($request->hasFile('image')) { $path = 'private_uploads/audits'; if (!Storage::exists($path)) Storage::makeDirectory($path, 775); $upload = $image = $request->file('image'); $ext = $image->getClientOriginalExtension(); $file_name = 'audit-'.str_random(18).'.'.$ext; Storage::putFileAs($path, $upload, $file_name); } $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name); return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success')); } } public function getRequestedIndex($user_id = null) { $requestedItems = CheckoutRequest::with('user', 'requestedItem')->whereNull('canceled_at')->with('user', 'requestedItem'); if ($user_id) { $requestedItems->where('user_id', $user_id)->get(); } $requestedItems = $requestedItems->orderBy('created_at', 'desc')->get(); return view('hardware/requested', compact('requestedItems')); } }