snipe-it/app/Http/Controllers/Api/AssetsController.php

811 lines
30 KiB
PHP
Raw Normal View History

2017-01-11 18:14:06 -08:00
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetRequest;
2017-11-27 21:17:16 -08:00
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Transformers\AssetsTransformer;
2017-01-11 18:14:06 -08:00
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\Location;
use App\Models\Setting;
2017-01-11 18:14:06 -08:00
use App\Models\User;
use Artisan;
use Auth;
use Carbon\Carbon;
use Config;
use DB;
use Gate;
2017-01-11 18:14:06 -08:00
use Illuminate\Http\Request;
use Input;
use Lang;
use Log;
use Mail;
use Paginator;
use Response;
use Slack;
use Str;
use TCPDF;
use Validator;
use View;
use App\Http\Transformers\SelectlistTransformer;
2017-01-11 18:14:06 -08:00
2017-01-11 18:14:06 -08:00
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
*
* @version v1.0
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
class AssetsController extends Controller
{
/**
* Returns JSON listing of all assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
*/
public function index(Request $request, $audit = null)
2017-01-11 18:14:06 -08:00
{
2017-01-13 11:41:00 -08:00
$this->authorize('index', Asset::class);
$settings = Setting::getSettings();
2017-01-13 11:41:00 -08:00
$allowed_columns = [
'id',
'name',
'asset_tag',
'serial',
'model_number',
'last_checkout',
'notes',
'expected_checkin',
'order_number',
'image',
'assigned_to',
'created_at',
'updated_at',
2017-01-13 11:41:00 -08:00
'purchase_date',
'purchase_cost',
'last_audit_date',
'next_audit_date',
'warranty_months',
'checkout_counter',
'checkin_counter',
'requests_counter',
2017-01-13 11:41:00 -08:00
];
2017-03-11 04:26:01 -08:00
$filter = array();
2019-05-23 17:39:50 -07:00
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
2017-03-11 04:26:01 -08:00
}
2017-01-13 11:41:00 -08:00
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[]=$field->db_column_name();
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets")
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
2019-02-13 04:45:21 -08:00
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
2017-10-18 10:07:35 -07:00
// 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.
2019-05-23 17:39:50 -07:00
if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id'));
}
WIP - Improved requested assets (#5289) * WIP - beginning of improved requested assets - Use Ajax tables for faster loading - Use new notifications for requesting an asset TODO: - Use ajax tables for requestable asset models - Use new notifications for canceling an asset request - Expire requests once the asset has been checked out to the requesting user * Only show asset name in email if it has one * Refactor requested method to only include non-canceled requests * Refactored requestable assets to log request and cancelation * Added softdeletes on checkout requests * Differentiate between canceling and deleting requests * Added asset request cancelation notification * Added timestamps and corrected unique key on requests table * Improved requests view * Re-use blade for cancel/request email * Refactored BS table formatter for requested assets * Location name min reduced to 2 * Added PAT test as maintenance option This needs to be refactored into database-driven options with a UI * Better slack message * Added getImageUrl method for assets * Include qty in request notifications TODO: - Try to pull requested info from original request for cancelation, otherwise it will default to 1 * Removed old asset request/cancel emails * Added user profile asset request routes * Added profile controller requested assets method * Added blade link to requested assets for profile view * Sort user history desc * Added requested assets blade * Added canceled at to checkoutRequest method * Include qty in request * Fixed comment, removed allowed_columns * Removed Queable methods, since we don’t use a queue * Fixed return type in method doc * Fixed version number * Changed id to user_id for clarity
2018-04-04 17:33:02 -07:00
if ($request->input('requestable')=='true') {
$assets->where('assets.requestable', '=', '1');
}
2019-05-23 17:39:50 -07:00
if ($request->filled('model_id')) {
$assets->InModelList([$request->input('model_id')]);
}
2019-05-23 17:39:50 -07:00
if ($request->filled('category_id')) {
$assets->InCategory($request->input('category_id'));
}
2019-05-23 17:39:50 -07:00
if ($request->filled('location_id')) {
2017-11-03 20:10:36 -07:00
$assets->where('assets.location_id', '=', $request->input('location_id'));
}
if ($request->filled('rtd_location_id')) {
$assets->where('assets.rtd_location_id', '=', $request->input('rtd_location_id'));
}
2019-05-23 17:39:50 -07:00
if ($request->filled('supplier_id')) {
2017-05-15 20:55:39 -07:00
$assets->where('assets.supplier_id', '=', $request->input('supplier_id'));
}
2019-05-23 17:39:50 -07:00
if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) {
$assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
2019-02-13 04:45:21 -08:00
->where('assets.assigned_type', '=', $request->input('assigned_type'));
}
2019-05-23 17:39:50 -07:00
if ($request->filled('company_id')) {
$assets->where('assets.company_id', '=', $request->input('company_id'));
}
2019-05-23 17:39:50 -07:00
if ($request->filled('manufacturer_id')) {
2017-02-03 19:52:00 -08:00
$assets->ByManufacturer($request->input('manufacturer_id'));
}
2019-05-23 17:39:50 -07:00
if ($request->filled('depreciation_id')) {
2017-10-17 11:20:05 -07:00
$assets->ByDepreciationId($request->input('depreciation_id'));
}
2019-05-23 17:39:50 -07:00
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
2017-01-11 18:14:06 -08:00
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
2017-01-13 11:41:00 -08:00
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
2017-01-11 18:14:06 -08:00
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
case 'due':
$assets->DueOrOverdueForAudit($settings);
break;
case 'overdue':
$assets->overdueForAudit($settings);
break;
}
}
2017-05-15 20:55:39 -07:00
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
// related to fulltext searches on complex queries.
// I am sad. :(
2017-02-08 03:37:44 -08:00
switch ($request->input('status')) {
2017-01-11 18:14:06 -08:00
case 'Deleted':
$assets->withTrashed()->Deleted();
2017-01-11 18:14:06 -08:00
break;
case 'Pending':
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',0)
->where('status_alias.pending','=',1)
->where('status_alias.archived', '=', 0);
});
2017-01-11 18:14:06 -08:00
break;
case 'RTD':
$assets->whereNull('assets.assigned_to')
2019-02-13 04:45:21 -08:00
->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',1)
->where('status_alias.pending','=',0)
->where('status_alias.archived', '=', 0);
});
2017-01-11 18:14:06 -08:00
break;
case 'Undeployable':
$assets->Undeployable();
break;
case 'Archived':
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',0)
->where('status_alias.pending','=',0)
->where('status_alias.archived', '=', 1);
});
2017-01-11 18:14:06 -08:00
break;
case 'Requestable':
$assets->where('assets.requestable', '=', 1)
->join('status_labels AS status_alias',function ($join) {
2019-02-13 04:45:21 -08:00
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',1)
->where('status_alias.pending','=',0)
->where('status_alias.archived', '=', 0);
});
2017-01-11 18:14:06 -08:00
break;
case 'Deployed':
// more sad, horrible workarounds for laravel bugs when doing full text searches
$assets->where('assets.assigned_to', '>', '0');
2017-01-11 18:14:06 -08:00
break;
2017-10-17 11:20:05 -07:00
default:
2019-05-23 17:39:50 -07:00
if ((!$request->filled('status_id')) && ($settings->show_archived_in_list!='1')) {
// terrible workaround for complex-query Laravel bug in fulltext
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.archived', '=', 0);
});
2019-02-13 04:45:21 -08:00
// If there is a status ID, don't take show_archived_in_list into consideration
} else {
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id");
});
}
2017-01-11 18:14:06 -08:00
}
if ((!is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
2019-05-23 17:39:50 -07:00
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
2017-01-11 18:14:06 -08:00
// This is kinda gross, but we need to do this because the Bootstrap Tables
// API passes custom field ordering as custom_fields.fieldname, and we have to strip
// that out to let the default sorter below order them correctly on the assets table.
$sort_override = str_replace('custom_fields.','', $request->input('sort')) ;
// This handles all of the pivot sorting (versus the assets.* fields
// in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
2019-02-13 04:45:21 -08:00
switch ($sort_override) {
2017-01-11 18:14:06 -08:00
case 'model':
2017-01-13 11:41:00 -08:00
$assets->OrderModels($order);
2017-01-11 18:14:06 -08:00
break;
case 'model_number':
2017-01-13 11:41:00 -08:00
$assets->OrderModelNumber($order);
2017-01-11 18:14:06 -08:00
break;
case 'category':
2017-01-13 11:41:00 -08:00
$assets->OrderCategory($order);
2017-01-11 18:14:06 -08:00
break;
case 'manufacturer':
2017-01-13 11:41:00 -08:00
$assets->OrderManufacturer($order);
2017-01-11 18:14:06 -08:00
break;
2017-01-13 11:41:00 -08:00
case 'company':
$assets->OrderCompany($order);
2017-01-11 18:14:06 -08:00
break;
case 'location':
2017-01-13 11:41:00 -08:00
$assets->OrderLocation($order);
2018-01-24 14:27:12 -08:00
case 'rtd_location':
$assets->OrderRtdLocation($order);
2017-01-11 18:14:06 -08:00
break;
case 'status_label':
2017-01-13 11:41:00 -08:00
$assets->OrderStatus($order);
2017-01-11 18:14:06 -08:00
break;
2017-05-15 20:55:39 -07:00
case 'supplier':
$assets->OrderSupplier($order);
break;
2017-01-11 18:14:06 -08:00
case 'assigned_to':
2017-01-13 11:41:00 -08:00
$assets->OrderAssigned($order);
2017-01-11 18:14:06 -08:00
break;
default:
2017-05-15 20:55:39 -07:00
$assets->orderBy($column_sort, $order);
2017-01-11 18:14:06 -08:00
break;
}
2017-01-13 11:41:00 -08:00
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformAssets($assets, $total);
2017-01-11 18:14:06 -08:00
}
/**
* Returns JSON with information about an asset (by tag) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $tag
* @since [v4.2.1]
* @return JsonResponse
*/
public function showByTag($tag)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->where('asset_tag',$tag)->first()) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
}
2019-02-04 18:58:28 -08:00
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
}
/**
* Returns JSON with information about an asset (by serial) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $serial
* @since [v4.2.1]
* @return JsonResponse
*/
public function showBySerial($serial)
{
$this->authorize('index', Asset::class);
if ($assets = Asset::with('assetstatus')->with('assignedTo')
->withTrashed()->where('serial',$serial)->get()) {
2019-02-13 04:45:21 -08:00
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
2019-02-04 18:58:28 -08:00
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
}
2019-02-13 04:45:21 -08:00
/**
2017-01-11 19:00:34 -08:00
* Returns JSON with information about an asset for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
2017-01-11 19:00:34 -08:00
*/
2017-01-12 02:19:55 -08:00
public function show($id)
2017-01-11 19:00:34 -08:00
{
2019-05-22 00:52:51 -07:00
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as userRequests_count')->findOrFail($id)) {
2017-01-11 19:00:34 -08:00
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
2017-01-11 19:00:34 -08:00
}
2017-01-11 19:00:34 -08:00
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$assets = Company::scopeCompanyables(Asset::select([
'assets.id',
'assets.name',
'assets.asset_tag',
'assets.model_id',
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id'
2019-02-13 04:45:21 -08:00
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(),'company_id', 'assets');
2019-05-23 17:39:50 -07:00
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
}
2019-05-23 17:39:50 -07:00
if ($request->filled('search')) {
$assets = $assets->AssignedSearch($request->input('search'));
}
$assets = $assets->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($assets as $asset) {
$asset->use_text = $asset->present()->fullName;
2017-11-22 15:07:34 -08:00
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
}
2019-02-13 04:45:21 -08:00
if ($asset->assetstatus->getStatuslabelType()=='pending') {
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
}
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;
}
return (new SelectlistTransformer)->transformSelectlist($assets);
}
2017-01-11 19:00:34 -08:00
/**
* Accepts a POST request to create a new asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
2017-01-12 02:19:55 -08:00
* @param Request $request
* @since [v4.0]
2017-01-12 02:19:55 -08:00
* @return JsonResponse
*/
public function store(AssetRequest $request)
{
2017-08-25 03:26:50 -07:00
$this->authorize('create', Asset::class);
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
$asset->name = $request->get('name');
$asset->serial = $request->get('serial');
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id'));
$asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = $request->get('status_id', 0);
$asset->warranty_months = $request->get('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
$asset->purchase_date = $request->get('purchase_date', null);
$asset->assigned_to = $request->get('assigned_to', null);
$asset->supplier_id = $request->get('supplier_id', 0);
$asset->requestable = $request->get('requestable', 0);
$asset->rtd_location_id = $request->get('rtd_location_id', null);
// 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());
}
}
}
if ($asset->save()) {
2017-10-31 05:38:52 -07:00
if ($request->get('assigned_user')) {
$target = User::find(request('assigned_user'));
} elseif ($request->get('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
} elseif ($request->get('assigned_location')) {
$target = Location::find(request('assigned_location'));
}
2017-01-12 02:19:55 -08:00
if (isset($target)) {
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
}
2017-08-25 03:26:50 -07:00
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
2017-01-12 03:48:18 -08:00
/**
* Accepts a POST request to update an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @since [v4.0]
* @return JsonResponse
*/
public function update(Request $request, $id)
{
$this->authorize('update', Asset::class);
2017-01-12 03:48:18 -08:00
if ($asset = Asset::find($id)) {
$asset->fill($request->all());
2019-05-23 17:39:50 -07:00
($request->filled('model_id')) ?
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
2019-05-23 17:39:50 -07:00
($request->filled('company_id')) ?
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : null;
2019-05-23 17:39:50 -07:00
($request->filled('rtd_location_id')) ?
$asset->location_id = $request->get('rtd_location_id') : null;
// Update custom fields
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->convertUnicodeDbSlug())) {
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());
}
2017-01-12 03:48:18 -08:00
}
}
}
2017-01-12 03:48:18 -08:00
if ($asset->save()) {
2019-05-23 17:39:50 -07:00
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
2019-02-13 04:45:21 -08:00
$location = $target->location_id;
2019-05-23 17:39:50 -07:00
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
2019-02-13 04:45:21 -08:00
$location = $target->location_id;
Asset::where('assigned_type', '\\App\\Models\\Asset')->where('assigned_to', $id)
->update(['location_id' => $target->location_id]);
2019-05-23 17:39:50 -07:00
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
2017-11-03 19:42:45 -07:00
$location = $target->id;
2017-01-12 03:48:18 -08:00
}
if (isset($target)) {
2017-11-03 19:42:45 -07:00
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
2017-01-12 03:48:18 -08:00
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
2017-01-12 03:48:18 -08:00
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
2017-01-12 03:48:18 -08:00
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
2017-01-12 03:48:18 -08:00
}
/**
* Delete a given asset (mark as deleted).
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
2017-01-12 02:19:55 -08:00
* @return JsonResponse
*/
public function destroy($id)
{
$this->authorize('delete', Asset::class);
if ($asset = Asset::find($id)) {
$this->authorize('delete', $asset);
DB::table('assets')
->where('id', $asset->id)
->update(array('assigned_to' => null));
$asset->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.delete.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
/**
* Checkout an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
*/
2017-11-27 21:17:16 -08:00
public function checkout(AssetCheckoutRequest $request, $asset_id)
{
$this->authorize('checkout', Asset::class);
$asset = Asset::findOrFail($asset_id);
if (!$asset->availableForCheckout()) {
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available')));
}
$this->authorize('checkout', $asset);
$error_payload = [];
$error_payload['asset'] = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
];
// This item is checked out to a location
if (request('checkout_to_type')=='location') {
$target = Location::find(request('assigned_location'));
$asset->location_id = ($target) ? $target->id : '';
$error_payload['target_id'] = $request->input('assigned_location');
$error_payload['target_type'] = 'location';
} elseif (request('checkout_to_type')=='asset') {
$target = Asset::where('id','!=',$asset_id)->find(request('assigned_asset'));
$asset->location_id = $target->rtd_location_id;
// Override with the asset's location_id if it has one
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_asset');
$error_payload['target_type'] = 'asset';
} elseif (request('checkout_to_type')=='user') {
// Fetch the target and set the asset's new location_id
$target = User::find(request('assigned_user'));
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_user');
$error_payload['target_type'] = 'user';
}
if (!isset($target)) {
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
}
$checkout_at = request('checkout_at', date("Y-m-d H:i:s"));
$expected_checkin = request('expected_checkin', null);
$note = request('note', null);
$asset_name = request('name', null);
2019-02-13 04:45:21 -08:00
// Set the location ID to the RTD location id if there is one
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
// TODO: Follow up here. WTF. Commented out for now.
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
// $asset->location_id = $target->rtd_location_id;
// }
if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
}
2019-08-22 21:36:47 -07:00
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
}
/**
* Checkin an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
*/
2017-11-03 19:42:45 -07:00
public function checkin(Request $request, $asset_id)
{
$this->authorize('checkin', Asset::class);
$asset = Asset::findOrFail($asset_id);
$this->authorize('checkin', $asset);
$user = $asset->assignedUser;
if (is_null($target = $asset->assignedTo)) {
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.already_checked_in')));
}
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
if ($request->filled('name')) {
$asset->name = $request->input('name');
}
2017-11-03 19:42:45 -07:00
$asset->location_id = $asset->rtd_location_id;
2019-05-23 17:39:50 -07:00
if ($request->filled('location_id')) {
2017-11-03 19:42:45 -07:00
$asset->location_id = $request->input('location_id');
}
if (Input::has('status_id')) {
Notification improvements (#5254) * Added “show fields in email” to custom fields * Added “show images in email” to settings * Added nicer HTML emails * Break notifications out into their own, instead of trying to mash them all together * Remove old notification for accessory checkout * Janky fix for #5076 - “The asset you have attempted to accept was not checked out to you” * Add method for image url for accessories * Added accessory checkout email blade * Make accessory email notification on checkout screen consistent with assets * Added native consumables notifications * Fixes for asset notification * Updated notification blades with correct-er fields * Updated notifications * License checkin notification - does not work yet Need to figure out whether the license seat is assigned to a person or an asset before we can pass the target * Added alternate “cc” email for admins * Only try to trigger notifications if the target is a user * Fix tests * Fixed consumable URL * Removed unused notification * Pass target type in params * Show slack status * Pass additional parameters There is a logic bug in this :( Will send to slack twice, since the admin CC and the user are both using the same notification. Fuckity fuck fuck fuck. * Pass a variable to the notification to supress the duplicate slack message * Slack is broken :( Trying to fix Will try a git bisect * Put preview back into checkout * Pulled old archaic mail * Removed debugging * Fixed wrong email title * Fixed slack endpoint not firing * Poobot, we hardly knew ye. * Removed old, manual mail from API * Typo :-/ * Code cleanup * Use defined formatted date in JSON * Use static properties for checkin/checkout notifiers for cleaner code * Removed debugging * Use date formatter * Fixed target_type * Fixed language in consumable email
2018-03-25 13:46:57 -07:00
$asset->status_id = Input::get('status_id');
}
if ($asset->save()) {
Notification improvements (#5254) * Added “show fields in email” to custom fields * Added “show images in email” to settings * Added nicer HTML emails * Break notifications out into their own, instead of trying to mash them all together * Remove old notification for accessory checkout * Janky fix for #5076 - “The asset you have attempted to accept was not checked out to you” * Add method for image url for accessories * Added accessory checkout email blade * Make accessory email notification on checkout screen consistent with assets * Added native consumables notifications * Fixes for asset notification * Updated notification blades with correct-er fields * Updated notifications * License checkin notification - does not work yet Need to figure out whether the license seat is assigned to a person or an asset before we can pass the target * Added alternate “cc” email for admins * Only try to trigger notifications if the target is a user * Fix tests * Fixed consumable URL * Removed unused notification * Pass target type in params * Show slack status * Pass additional parameters There is a logic bug in this :( Will send to slack twice, since the admin CC and the user are both using the same notification. Fuckity fuck fuck fuck. * Pass a variable to the notification to supress the duplicate slack message * Slack is broken :( Trying to fix Will try a git bisect * Put preview back into checkout * Pulled old archaic mail * Removed debugging * Fixed wrong email title * Fixed slack endpoint not firing * Poobot, we hardly knew ye. * Removed old, manual mail from API * Typo :-/ * Code cleanup * Use defined formatted date in JSON * Use static properties for checkin/checkout notifiers for cleaner code * Removed debugging * Use date formatter * Fixed target_type * Fixed language in consumable email
2018-03-25 13:46:57 -07:00
$asset->logCheckin($target, e(request('note')));
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
}
/**
* Mark an asset as audited
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return JsonResponse
*/
public function audit(Request $request) {
$this->authorize('audit', Asset::class);
$rules = array(
'asset_tag' => 'required',
2017-08-25 18:40:20 -07:00
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable'
);
$validator = Validator::make($request->all(), $rules);
2017-08-25 18:40:20 -07:00
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::where('asset_tag','=', $request->input('asset_tag'))->first();
if ($asset) {
// 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');
if ($asset->save()) {
$log = $asset->logAudit(request('note'),request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag),
'note'=> e($request->input('note')),
'next_audit_date' => Helper::getFormattedDateObject($log->calcNextAuditDate())
], trans('admin/hardware/message.audit.success')));
}
}
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.$request->input('asset_tag').' not found'));
2017-08-25 18:40:20 -07:00
WIP - Improved requested assets (#5289) * WIP - beginning of improved requested assets - Use Ajax tables for faster loading - Use new notifications for requesting an asset TODO: - Use ajax tables for requestable asset models - Use new notifications for canceling an asset request - Expire requests once the asset has been checked out to the requesting user * Only show asset name in email if it has one * Refactor requested method to only include non-canceled requests * Refactored requestable assets to log request and cancelation * Added softdeletes on checkout requests * Differentiate between canceling and deleting requests * Added asset request cancelation notification * Added timestamps and corrected unique key on requests table * Improved requests view * Re-use blade for cancel/request email * Refactored BS table formatter for requested assets * Location name min reduced to 2 * Added PAT test as maintenance option This needs to be refactored into database-driven options with a UI * Better slack message * Added getImageUrl method for assets * Include qty in request notifications TODO: - Try to pull requested info from original request for cancelation, otherwise it will default to 1 * Removed old asset request/cancel emails * Added user profile asset request routes * Added profile controller requested assets method * Added blade link to requested assets for profile view * Sort user history desc * Added requested assets blade * Added canceled at to checkoutRequest method * Include qty in request * Fixed comment, removed allowed_columns * Removed Queable methods, since we don’t use a queue * Fixed return type in method doc * Fixed version number * Changed id to user_id for clarity
2018-04-04 17:33:02 -07:00
}
/**
* Returns JSON listing of all requestable assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return JsonResponse
*/
public function requestable(Request $request)
{
$this->authorize('viewRequestable', Asset::class);
WIP - Improved requested assets (#5289) * WIP - beginning of improved requested assets - Use Ajax tables for faster loading - Use new notifications for requesting an asset TODO: - Use ajax tables for requestable asset models - Use new notifications for canceling an asset request - Expire requests once the asset has been checked out to the requesting user * Only show asset name in email if it has one * Refactor requested method to only include non-canceled requests * Refactored requestable assets to log request and cancelation * Added softdeletes on checkout requests * Differentiate between canceling and deleting requests * Added asset request cancelation notification * Added timestamps and corrected unique key on requests table * Improved requests view * Re-use blade for cancel/request email * Refactored BS table formatter for requested assets * Location name min reduced to 2 * Added PAT test as maintenance option This needs to be refactored into database-driven options with a UI * Better slack message * Added getImageUrl method for assets * Include qty in request notifications TODO: - Try to pull requested info from original request for cancelation, otherwise it will default to 1 * Removed old asset request/cancel emails * Added user profile asset request routes * Added profile controller requested assets method * Added blade link to requested assets for profile view * Sort user history desc * Added requested assets blade * Added canceled at to checkoutRequest method * Include qty in request * Fixed comment, removed allowed_columns * Removed Queable methods, since we don’t use a queue * Fixed return type in method doc * Fixed version number * Changed id to user_id for clarity
2018-04-04 17:33:02 -07:00
$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);
}
2017-01-11 18:14:06 -08:00
}