Merge remote-tracking branch 'origin/develop'

This commit is contained in:
snipe 2023-08-22 12:41:27 +01:00
commit bb61134dd5
10 changed files with 299 additions and 36 deletions

View file

@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\CustomField;
class BulkAssetsController extends Controller
{
@ -42,6 +43,17 @@ class BulkAssetsController extends Controller
$asset_ids = array_values(array_unique($request->input('ids')));
//custom fields logic
$asset_custom_field = Asset::with(['model.fieldset.fields', 'model'])->whereIn('id', $asset_ids)->whereHas('model', function ($query) {
return $query->where('fieldset_id', '!=', null);
})->get();
$models = $asset_custom_field->unique('model_id');
$modelNames = [];
foreach($models as $model) {
$modelNames[] = $model->model->name;
}
if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) {
case 'labels':
@ -74,7 +86,9 @@ class BulkAssetsController extends Controller
$this->authorize('update', Asset::class);
return view('hardware/bulk')
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList());
->with('statuslabel_list', Helper::statusLabelList())
->with('models', $models->pluck(['model']))
->with('modelNames', $modelNames);
}
}
@ -92,6 +106,7 @@ class BulkAssetsController extends Controller
public function update(Request $request)
{
$this->authorize('update', Asset::class);
$error_bag = [];
// Get the back url from the session and then destroy the session
$bulk_back_url = route('hardware.index');
@ -100,12 +115,21 @@ class BulkAssetsController extends Controller
}
if (! $request->filled('ids') || count($request->input('ids')) <= 0) {
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
if(Session::exists('ids')) {
$assets = Session::get('ids');
} elseif (! $request->filled('ids') || count($request->input('ids')) <= 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
$assets = array_keys($request->input('ids'));
if ($request->anyFilled($custom_field_columns)) {
$custom_fields_present = true;
} else {
$custom_fields_present = false;
}
if (($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
@ -121,6 +145,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
|| ($request->anyFilled($custom_field_columns))
) {
foreach ($assets as $assetId) {
@ -136,6 +161,9 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date');
foreach ($custom_field_columns as $key => $custom_field_column) {
$this->conditionallyAddItem($custom_field_column);
}
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
@ -168,11 +196,11 @@ class BulkAssetsController extends Controller
}
$changed = [];
$asset = Asset::where('id' ,$assetId)->get();
$assetCollection = Asset::where('id' ,$assetId)->get();
foreach ($this->update_array as $key => $value) {
if ($this->update_array[$key] != $asset->toArray()[0][$key]) {
$changed[$key]['old'] = $asset->toArray()[0][$key];
if ($this->update_array[$key] != $assetCollection->toArray()[0][$key]) {
$changed[$key]['old'] = $assetCollection->toArray()[0][$key];
$changed[$key]['new'] = $this->update_array[$key];
}
}
@ -185,16 +213,46 @@ class BulkAssetsController extends Controller
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
DB::table('assets')
->where('id', $assetId)
->update($this->update_array);
} // endforeach
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
if($custom_fields_present) {
$asset = Asset::find($assetId);
$assetCustomFields = $asset->model()->first()->fieldset;
if($assetCustomFields && $assetCustomFields->fields) {
foreach ($assetCustomFields->fields as $field) {
if (array_key_exists($field->db_column, $this->update_array)) {
$asset->{$field->db_column} = $this->update_array[$field->db_column];
$saved = $asset->save();
if(!$saved) {
$error_bag[] = $asset->getErrors();
}
continue;
} else {
$array = $this->update_array;
array_except($array, $field->db_column);
$asset->save($array);
}
if (!$asset->save()) {
$error_bag[] = $asset->getErrors();
}
}
}
} else {
Asset::find($assetId)->update($this->update_array);
}
}
if(!empty($error_bag)) {
$errors = [];
//find the customfield name from the name of the messagebag items
foreach ($error_bag as $key => $bag) {
foreach($bag->keys() as $key => $value) {
CustomField::where('db_column', $value)->get()->map(function($item) use (&$errors) {
$errors[] = $item->name;
});
}
}
return redirect($bulk_back_url)->with('bulk_errors', array_unique($errors));
}
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
}
// no values given, nothing to update
return redirect($bulk_back_url)->with('warning', trans('admin/hardware/message.update.nothing_updated'));
}

View file

@ -4,6 +4,10 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\Setting;
use App\Models\Company;
use App\Models\Supplier;
use App\Models\Location;
use App\Models\AssetModel;
use Illuminate\Database\Eloquent\Collection;
class ActionlogsTransformer
@ -53,6 +57,7 @@ class ActionlogsTransformer
}
}
$clean_meta= $this->changedInfo($clean_meta);
}
$file_url = '';
@ -132,6 +137,49 @@ class ActionlogsTransformer
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
/**
* This takes the ids of the changed attributes and returns the names instead for the history view of an Asset
*
* @param array $clean_meta
* @return array
*/
public function changedInfo(array $clean_meta)
{
if(array_key_exists('rtd_location_id',$clean_meta)) {
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". Location::find($clean_meta['rtd_location_id']['old'])->name : trans('general.unassigned');
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". Location::find($clean_meta['rtd_location_id']['new'])->name : trans('general.unassigned');
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
unset($clean_meta['rtd_location_id']);
}
if(array_key_exists('location_id', $clean_meta)) {
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ".Location::find($clean_meta['location_id']['old'])->name : trans('general.unassigned');
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ".Location::find($clean_meta['location_id']['new'])->name : trans('general.unassigned');
$clean_meta['Current Location'] = $clean_meta['location_id'];
unset($clean_meta['location_id']);
}
if(array_key_exists('model_id', $clean_meta)) {
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".AssetModel::find($clean_meta['model_id']['old'])->name;
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".AssetModel::find($clean_meta['model_id']['new'])->name; /* model is required at asset creation */
$clean_meta['Model'] = $clean_meta['model_id'];
unset($clean_meta['model_id']);
}
if(array_key_exists('company_id', $clean_meta)) {
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."]".Company::find($clean_meta['company_id']['old'])->name : trans('general.unassigned');
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ".Company::find($clean_meta['company_id']['new'])->name : trans('general.unassigned');
$clean_meta['Company'] = $clean_meta['company_id'];
unset($clean_meta['company_id']);
}
if(array_key_exists('supplier_id', $clean_meta)) {
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ".Supplier::find($clean_meta['supplier_id']['old'])->name : trans('general.unassigned');
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ".Supplier::find($clean_meta['supplier_id']['new'])->name : trans('general.unassigned');
$clean_meta['Supplier'] = $clean_meta['supplier_id'];
unset($clean_meta['supplier_id']);
}
return $clean_meta;
}

View file

@ -151,6 +151,11 @@ class AssetModel extends SnipeModel
return $this->belongsTo(\App\Models\CustomFieldset::class, 'fieldset_id');
}
public function customFields()
{
return $this->fieldset()->first()->fields();
}
/**
* Establishes the model -> custom field default values relationship
*

View file

@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rule;
use Schema;
use Watson\Validating\ValidatingTrait;
class CustomField extends Model
{
use HasFactory;
@ -183,6 +182,11 @@ class CustomField extends Model
return $this->belongsToMany(\App\Models\CustomFieldset::class);
}
public function assetModels()
{
return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id');
}
/**
* Establishes the customfield -> admin user relationship
*

38
composer.lock generated
View file

@ -2948,16 +2948,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "2.4.0",
"version": "2.4.5",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "13388f00956b1503577598873fffb5ae994b5737"
"reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737",
"reference": "13388f00956b1503577598873fffb5ae994b5737",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66",
"reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66",
"shasum": ""
},
"require": {
@ -2971,17 +2971,18 @@
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"bamarni/composer-bin-plugin": "^1.8.1",
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.8 || ^9.3.10"
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
@ -3043,7 +3044,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.4.0"
"source": "https://github.com/guzzle/psr7/tree/2.4.5"
},
"funding": [
{
@ -3059,7 +3060,7 @@
"type": "tidelift"
}
],
"time": "2022-06-20T21:43:11+00:00"
"time": "2023-04-17T16:00:45+00:00"
},
{
"name": "intervention/image",
@ -6130,16 +6131,16 @@
},
{
"name": "nyholm/psr7",
"version": "1.5.1",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/Nyholm/psr7.git",
"reference": "f734364e38a876a23be4d906a2a089e1315be18a"
"reference": "e874c8c4286a1e010fb4f385f3a55ac56a05cc93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/f734364e38a876a23be4d906a2a089e1315be18a",
"reference": "f734364e38a876a23be4d906a2a089e1315be18a",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/e874c8c4286a1e010fb4f385f3a55ac56a05cc93",
"reference": "e874c8c4286a1e010fb4f385f3a55ac56a05cc93",
"shasum": ""
},
"require": {
@ -6149,6 +6150,7 @@
"psr/http-message": "^1.0"
},
"provide": {
"php-http/message-factory-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
@ -6161,7 +6163,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.6-dev"
}
},
"autoload": {
@ -6191,7 +6193,7 @@
],
"support": {
"issues": "https://github.com/Nyholm/psr7/issues",
"source": "https://github.com/Nyholm/psr7/tree/1.5.1"
"source": "https://github.com/Nyholm/psr7/tree/1.6.1"
},
"funding": [
{
@ -6203,7 +6205,7 @@
"type": "github"
}
],
"time": "2022-06-22T07:13:36+00:00"
"time": "2023-04-17T16:03:48+00:00"
},
{
"name": "onelogin/php-saml",
@ -16736,5 +16738,5 @@
"ext-pdo": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.3.0"
}

View file

@ -10,6 +10,9 @@ return [
'bulk_update' => 'Bulk Update Assets',
'bulk_update_help' => 'This form allows you to update multiple assets at once. Only fill in the fields you need to change. Any fields left blank will remain unchanged. ',
'bulk_update_warn' => 'You are about to edit the properties of a single asset.|You are about to edit the properties of :asset_count assets.',
'bulk_update_with_custom_field' => 'Note the assets are :asset_model_count different types of models.',
'bulk_update_model_prefix' => 'On Models',
'bulk_update_custom_field_unique' => 'This is a unique field and can not be bulk edited.',
'checkedout_to' => 'Checked Out To',
'checkout_date' => 'Checkout Date',
'checkin_date' => 'Checkin Date',

View file

@ -283,6 +283,7 @@ return [
'user' => 'User',
'accepted' => 'accepted',
'declined' => 'declined',
'unassigned' => 'Unassigned',
'unaccepted_asset_report' => 'Unaccepted Assets',
'users' => 'Users',
'viewall' => 'View All',
@ -364,6 +365,7 @@ return [
'licenses_count' => 'Licenses Count',
'notification_error' => 'Error:',
'notification_error_hint' => 'Please check the form below for errors',
'notification_bulk_error_hint' => 'The following fields had validation errors and were not edited:',
'notification_success' => 'Success:',
'notification_warning' => 'Warning:',
'notification_info' => 'Info:',

View file

@ -21,6 +21,9 @@
<div class="callout callout-warning">
<i class="fas fa-exclamation-triangle"></i> {{ trans_choice('admin/hardware/form.bulk_update_warn', count($assets), ['asset_count' => count($assets)]) }}
@if (count($models) > 0)
{{ trans_choice('admin/hardware/form.bulk_update_with_custom_field', count($models), ['asset_model_count' => count($models)]) }}
@endif
</div>
<form class="form-horizontal" method="post" action="{{ route('hardware/bulksave') }}" autocomplete="off" role="form">
@ -182,6 +185,8 @@
</div>
</div>
@include("models/custom_fields_form_bulk_edit",["models" => $models])
@foreach ($assets as $key => $value)
<input type="hidden" name="ids[{{ $value }}]" value="1">
@endforeach

View file

@ -0,0 +1,119 @@
@php
//set array up before loop so it doesn't get wiped at every iteration
$fields = [];
@endphp
@foreach($models as $model)
@if (($model) && ($model->fieldset))
@foreach($model->fieldset->fields AS $field)
@php
//prevents some duplicate queries - open to a better way of skipping dupes in output
//its ugly, but if we'd rather deal with duplicate queries we can get rid of this.
if (in_array($field->db_column_name(), $fields)) {
$duplicate = true;
continue;
} else {
$duplicate = false;
}
$fields[] = $field->db_column_name();
@endphp
<div class="form-group{{ $errors->has($field->db_column_name()) ? ' has-error' : '' }}">
<label for="{{ $field->db_column_name() }}" class="col-md-3 control-label">{{ $field->name }} </label>
<div class="col-md-7 col-sm-12{{ ($field->pivot->required=='1') ? ' required' : '' }}">
@if ($field->element!='text')
<!-- Listbox -->
@if ($field->element=='listbox')
{{ Form::select($field->db_column_name(), $field->formatFieldValuesAsArray(),
Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, htmlspecialchars($item->{$field->db_column_name()}, ENT_QUOTES)) : $field->defaultValue($model->id))), ['class'=>'format select2 form-control']) }}
@elseif ($field->element=='textarea')
@if($field->is_unique)
<input type="text" class="form-control" disabled value="{{ trans('/admin/hardware/form.bulk_update_custom_field_unique') }}">
@endif
@if(!$field->is_unique)
<textarea class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}">{{ Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
@endif
@elseif ($field->element=='checkbox')
<!-- Checkboxes -->
@foreach ($field->formatFieldValuesAsArray() as $key => $value)
<div>
<label>
<input type="checkbox" value="{{ $value }}" name="{{ $field->db_column_name() }}[]" class="minimal" {{ isset($item) ? (in_array($value, array_map('trim', explode(',', $item->{$field->db_column_name()}))) ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($key, array_map('trim', explode(',', $field->defaultValue($model->id)))) ? ' checked="checked"' : '')) }}>
{{ $value }}
</label>
</div>
@endforeach
@elseif ($field->element=='radio')
@foreach ($field->formatFieldValuesAsArray() as $value)
<div>
<label>
<input type="radio" value="{{ $value }}" name="{{ $field->db_column_name() }}" class="minimal" {{ isset($item) ? ($item->{$field->db_column_name()} == $value ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($value, explode(', ', $field->defaultValue($model->id))) ? ' checked="checked"' : '')) }}>
{{ $value }}
</label>
</div>
@endforeach
@endif
@else
<!-- Date field -->
@if ($field->format=='DATE')
<div class="input-group col-md-5" style="padding-left: 0px;">
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true" data-date-clear-btn="true">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="{{ $field->db_column_name() }}" id="{{ $field->db_column_name() }}" readonly value="{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}" style="background-color:inherit">
<span class="input-group-addon"><i class="fas fa-calendar" aria-hidden="true"></i></span>
</div>
</div>
@else
@if (($field->field_encrypted=='0') || (Gate::allows('admin')))
@if ($field->is_unique)
<input type="text" class="form-control" disabled value="{{trans('/admin/hardware/form.bulk_update_custom_field_unique')}}">
@endif
@if(!$field->is_unique)
<input type="text" value="{{ Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}" id="{{ $field->db_column_name() }}" class="form-control" name="{{ $field->db_column_name() }}" placeholder="Enter {{ strtolower($field->format) }} text">
@endif
@else
<input type="text" value="{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}" class="form-control disabled" disabled>
@endif
@endif
@endif
@if ($field->help_text!='')
<p class="help-block">{{ $field->help_text }}</p>
@endif
<p>{{ trans('admin/hardware/form.bulk_update_model_prefix') }}:
{{$field->assetModels()->pluck('name')->intersect($modelNames)->implode(', ')}}
</p>
<?php
$errormessage=$errors->first($field->db_column_name());
if ($errormessage) {
$errormessage=preg_replace('/ snipeit /', '', $errormessage);
print('<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> '.$errormessage.'</span>');
}
?>
</div>
@if ($field->field_encrypted)
<div class="col-md-1 col-sm-1 text-left">
<i class="fas fa-lock" data-toggle="tooltip" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}"></i>
</div>
@endif
</div>
@endforeach
@endif
@endforeach

View file

@ -115,6 +115,23 @@
@endif
@if ($messages = Session::get('bulk_errors'))
<div class="col-md-12">
<div class="alert alert alert-danger fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
<strong>{{ trans('general.notification_error') }} </strong>
{{ trans('general.notification_bulk_error_hint') }}
@foreach($messages as $message)
<ul>
<li>{{ $message }}</li>
</ul>
@endforeach
</div>
</div>
@endif
@if ($message = Session::get('warning'))
<div class="col-md-12">
<div class="alert alert-warning fade in">