mirror of
https://github.com/snipe/snipe-it.git
synced 2024-11-09 23:24:06 -08:00
Merge pull request #13800 from snipe/fixes/bulk_edit_assets
Fixed FD-38641 - Bulk asset edit unable to update model_id, misc other bugs
This commit is contained in:
commit
aab7eb4a85
|
@ -112,7 +112,8 @@ class BulkAssetsController extends Controller
|
|||
public function update(Request $request)
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$error_bag = [];
|
||||
$has_errors = 0;
|
||||
$error_array = array();
|
||||
|
||||
// Get the back url from the session and then destroy the session
|
||||
$bulk_back_url = route('hardware.index');
|
||||
|
@ -120,10 +121,9 @@ class BulkAssetsController extends Controller
|
|||
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
||||
}
|
||||
|
||||
|
||||
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
||||
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
||||
|
||||
if(Session::exists('ids')) {
|
||||
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'));
|
||||
|
@ -160,7 +160,6 @@ class BulkAssetsController extends Controller
|
|||
|
||||
$this->conditionallyAddItem('purchase_date')
|
||||
->conditionallyAddItem('expected_checkin')
|
||||
->conditionallyAddItem('model_id')
|
||||
->conditionallyAddItem('order_number')
|
||||
->conditionallyAddItem('requestable')
|
||||
->conditionallyAddItem('status_id')
|
||||
|
@ -187,6 +186,7 @@ class BulkAssetsController extends Controller
|
|||
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$this->update_array['company_id'] = $request->input('company_id');
|
||||
if ($request->input('company_id') == 'clear') {
|
||||
|
@ -208,61 +208,95 @@ class BulkAssetsController extends Controller
|
|||
}
|
||||
|
||||
$changed = [];
|
||||
$assetCollection = Asset::where('id' ,$assetId)->get();
|
||||
$asset = Asset::find($assetId);
|
||||
|
||||
foreach ($this->update_array as $key => $value) {
|
||||
if ($this->update_array[$key] != $assetCollection->toArray()[0][$key]) {
|
||||
$changed[$key]['old'] = $assetCollection->toArray()[0][$key];
|
||||
if ($this->update_array[$key] != $asset->{$key}) {
|
||||
$changed[$key]['old'] = $asset->{$key};
|
||||
$changed[$key]['new'] = $this->update_array[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Asset::class;
|
||||
$logAction->item_id = $assetId;
|
||||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->log_meta = json_encode($changed);
|
||||
$logAction->logaction('update');
|
||||
|
||||
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;
|
||||
});
|
||||
if ($custom_fields_present) {
|
||||
|
||||
$model = $asset->model()->first();
|
||||
|
||||
// Use the rules of the new model fieldsets if the model changed
|
||||
if ($request->filled('model_id')) {
|
||||
$this->update_array['model_id'] = $request->input('model_id');
|
||||
$model = \App\Models\AssetModel::find($request->input('model_id'));
|
||||
}
|
||||
}
|
||||
return redirect($bulk_back_url)->with('bulk_errors', array_unique($errors));
|
||||
}
|
||||
|
||||
|
||||
// Make sure this model is valid
|
||||
$assetCustomFields = ($model) ? $model->fieldset : null;
|
||||
|
||||
if ($assetCustomFields && $assetCustomFields->fields) {
|
||||
|
||||
foreach ($assetCustomFields->fields as $field) {
|
||||
|
||||
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted=='1')) {
|
||||
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
|
||||
|
||||
/*
|
||||
* Check if the decrypted existing value is different from one we just submitted
|
||||
* and if not, pull it out of the object since it shouldn't really be updating at all.
|
||||
* If we don't do this, it will try to re-encrypt it, and the same value encrypted two
|
||||
* different times will have different values, so it will *look* like it was updated
|
||||
* but it wasn't.
|
||||
*/
|
||||
if ($decrypted_old != $this->update_array[$field->db_column]) {
|
||||
$asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]);
|
||||
} else {
|
||||
/*
|
||||
* Remove the encrypted custom field from the update_array, since nothing changed
|
||||
*/
|
||||
unset($this->update_array[$field->db_column]);
|
||||
unset($asset->{$field->db_column});
|
||||
}
|
||||
|
||||
/*
|
||||
* These custom fields aren't encrypted, just carry on as usual
|
||||
*/
|
||||
} else {
|
||||
|
||||
|
||||
if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
|
||||
|
||||
// Check if this is an array, and if so, flatten it
|
||||
if (is_array($this->update_array[$field->db_column])) {
|
||||
$asset->{$field->db_column} = implode(', ', $this->update_array[$field->db_column]);
|
||||
} else {
|
||||
$asset->{$field->db_column} = $this->update_array[$field->db_column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // endforeach
|
||||
} // end custom field check
|
||||
} // end custom fields handler
|
||||
|
||||
|
||||
|
||||
// Check if it passes validation, and then try to save
|
||||
if (!$asset->update($this->update_array)) {
|
||||
|
||||
// Build the error array
|
||||
foreach ($asset->getErrors()->toArray() as $key => $message) {
|
||||
for ($x = 0; $x < count($message); $x++) {
|
||||
$error_array[$key][] = trans('general.asset') . ' ' . $asset->id . ': ' . $message[$x];
|
||||
$has_errors++;
|
||||
}
|
||||
}
|
||||
|
||||
} // end if saved
|
||||
|
||||
} // end asset foreach
|
||||
|
||||
if ($has_errors > 0) {
|
||||
return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
|
||||
}
|
||||
|
||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
|
||||
}
|
||||
// no values given, nothing to update
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace App\Http\Transformers;
|
|||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Company;
|
||||
|
@ -12,6 +13,7 @@ use App\Models\AssetModel;
|
|||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class ActionlogsTransformer
|
||||
{
|
||||
|
@ -98,6 +100,13 @@ class ActionlogsTransformer
|
|||
\Log::debug('custom fields do not match');
|
||||
$clean_meta[$fieldname]['old'] = "************";
|
||||
$clean_meta[$fieldname]['new'] = "************";
|
||||
|
||||
// Display the changes if the user is an admin or superadmin
|
||||
if (Gate::allows('admin')) {
|
||||
$clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): '';
|
||||
$clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use Carbon\Carbon;
|
|||
class AssetObserver
|
||||
{
|
||||
/**
|
||||
* Listen to the User created event.
|
||||
* Listen to the Asset updating event. This fires automatically every time an existing asset is saved.
|
||||
*
|
||||
* @param Asset $asset
|
||||
* @return void
|
||||
|
|
|
@ -38,7 +38,7 @@ class ActionlogFactory extends Factory
|
|||
{
|
||||
return $this->state(function () {
|
||||
$target = User::inRandomOrder()->first();
|
||||
$asset = Asset::RTD()->inRandomOrder()->first();
|
||||
$asset = Asset::inRandomOrder()->RTD()->first();
|
||||
|
||||
$asset->update(
|
||||
[
|
||||
|
|
|
@ -26,6 +26,7 @@ class CustomFieldFactory extends Factory
|
|||
'format' => '',
|
||||
'element' => 'text',
|
||||
'auto_add_to_fieldsets' => '0',
|
||||
'show_in_requestable_list' => '0',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,7 @@ class CustomFieldFactory extends Factory
|
|||
return [
|
||||
'name' => 'CPU',
|
||||
'help_text' => 'The speed of the processor on this device.',
|
||||
'show_in_requestable_list' => '1',
|
||||
];
|
||||
});
|
||||
}
|
||||
|
@ -79,4 +81,37 @@ class CustomFieldFactory extends Factory
|
|||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function testEncrypted()
|
||||
{
|
||||
return $this->state(function () {
|
||||
return [
|
||||
'name' => 'Test Encrypted',
|
||||
'field_encrypted' => '1',
|
||||
'help_text' => 'This is a sample encrypted field.',
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function testCheckbox()
|
||||
{
|
||||
return $this->state(function () {
|
||||
return [
|
||||
'name' => 'Test Checkbox',
|
||||
'help_text' => 'This is a sample checkbox.',
|
||||
'field_values' => "One\nTwo\nThree",
|
||||
'element' => 'checkbox',
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function testRequired()
|
||||
{
|
||||
return $this->state(function () {
|
||||
return [
|
||||
'name' => 'Test Required',
|
||||
'help_text' => 'This is a sample required field.',
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,10 @@ class CustomFieldSeeder extends Seeder
|
|||
CustomField::factory()->count(1)->ram()->create();
|
||||
CustomField::factory()->count(1)->cpu()->create();
|
||||
CustomField::factory()->count(1)->macAddress()->create();
|
||||
CustomField::factory()->count(1)->testEncrypted()->create();
|
||||
CustomField::factory()->count(1)->testCheckbox()->create();
|
||||
CustomField::factory()->count(1)->testRequired()->create();
|
||||
|
||||
|
||||
DB::table('custom_field_custom_fieldset')->insert([
|
||||
[
|
||||
|
@ -66,6 +70,40 @@ class CustomFieldSeeder extends Seeder
|
|||
'required' => 0,
|
||||
],
|
||||
|
||||
[
|
||||
'custom_field_id' => '6',
|
||||
'custom_fieldset_id' => '2',
|
||||
'order' => 0,
|
||||
'required' => 0,
|
||||
],
|
||||
|
||||
[
|
||||
'custom_field_id' => '6',
|
||||
'custom_fieldset_id' => '1',
|
||||
'order' => 0,
|
||||
'required' => 0,
|
||||
],
|
||||
|
||||
[
|
||||
'custom_field_id' => '7',
|
||||
'custom_fieldset_id' => '2',
|
||||
'order' => 0,
|
||||
'required' => 0,
|
||||
],
|
||||
[
|
||||
'custom_field_id' => '7',
|
||||
'custom_fieldset_id' => '1',
|
||||
'order' => 0,
|
||||
'required' => 0,
|
||||
],
|
||||
|
||||
[
|
||||
'custom_field_id' => '8',
|
||||
'custom_fieldset_id' => '1',
|
||||
'order' => 0,
|
||||
'required' => 1,
|
||||
],
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,12 +38,13 @@ class DatabaseSeeder extends Seeder
|
|||
$this->call(DepreciationSeeder::class);
|
||||
$this->call(StatuslabelSeeder::class);
|
||||
$this->call(AccessorySeeder::class);
|
||||
$this->call(CustomFieldSeeder::class);
|
||||
$this->call(AssetSeeder::class);
|
||||
$this->call(LicenseSeeder::class);
|
||||
$this->call(ComponentSeeder::class);
|
||||
$this->call(ConsumableSeeder::class);
|
||||
$this->call(ActionlogSeeder::class);
|
||||
$this->call(CustomFieldSeeder::class);
|
||||
|
||||
|
||||
Artisan::call('snipeit:sync-asset-locations', ['--output' => 'all']);
|
||||
$output = Artisan::output();
|
||||
|
|
|
@ -368,7 +368,7 @@ return [
|
|||
'consumables_count' => 'Consumables Count',
|
||||
'components_count' => 'Components Count',
|
||||
'licenses_count' => 'Licenses Count',
|
||||
'notification_error' => 'Error:',
|
||||
'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',
|
||||
|
|
|
@ -151,6 +151,7 @@
|
|||
<th data-visible="false" data-sortable="true" class="text-center"><i class="fa fa-eye" aria-hidden="true"><span class="sr-only">Visible to User</span></i></th>
|
||||
<th data-sortable="true" data-searchable="true" class="text-center"><i class="fa fa-envelope" aria-hidden="true"><span class="sr-only">{{ trans('admin/custom_fields/general.show_in_email_short') }}</span></i></th>
|
||||
<th data-sortable="true" data-searchable="true" class="text-center"><i class="fa fa-laptop fa-fw" aria-hidden="true"><span class="sr-only">{{ trans('admin/custom_fields/general.show_in_requestable_list_short') }}</span></i></th>
|
||||
<th data-sortable="true" data-searchable="true" class="text-center"><i class="fa-solid fa-fingerprint"><span class="sr-only">{{ trans('admin/custom_fields/general.unique') }}</span></i></th>
|
||||
<th data-sortable="true" data-searchable="true" class="text-center">{{ trans('admin/custom_fields/general.field_element_short') }}</th>
|
||||
<th data-searchable="true">{{ trans('admin/custom_fields/general.fieldsets') }}</th>
|
||||
<th>{{ trans('button.actions') }}</th>
|
||||
|
@ -176,6 +177,7 @@
|
|||
<td class="text-center">{!! ($field->display_in_user_view=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
|
||||
<td class="text-center">{!! ($field->show_in_email=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->show_in_requestable_list=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->is_unique=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td>{{ $field->element }}</td>
|
||||
<td>
|
||||
@foreach($field->fieldset as $fieldset)
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-control">
|
||||
{{ Form::checkbox('null_expected_checkin_date', '1', false, ['checked' => 'false']) }}
|
||||
{{ Form::checkbox('null_expected_checkin_date', '1', false) }}
|
||||
{{ trans_choice('general.set_to_null', count($assets), ['asset_count' => count($assets)]) }}
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -37,21 +37,20 @@
|
|||
@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>
|
||||
<label class="form-control">
|
||||
<input type="checkbox" value="{{ $value }}" name="{{ $field->db_column_name() }}[]" {{ 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>
|
||||
|
||||
@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"' : '')) }}>
|
||||
|
||||
<label class="form-control">
|
||||
<input type="radio" value="{{ $value }}" name="{{ $field->db_column_name() }}" {{ 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
|
||||
|
|
|
@ -115,17 +115,19 @@
|
|||
@endif
|
||||
|
||||
|
||||
@if ($messages = Session::get('bulk_errors'))
|
||||
@if ($messages = Session::get('bulk_asset_errors'))
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert alert-danger fade in">
|
||||
<button type="button" class="close" data-dismiss="alert">×</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)
|
||||
@foreach($messages as $key => $message)
|
||||
@for ($x = 0; $x < count($message); $x++)
|
||||
<ul>
|
||||
<li>{{ $message }}</li>
|
||||
</ul>
|
||||
<li>{{ $message[$x] }}</li>
|
||||
</ul>
|
||||
@endfor
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -530,15 +530,37 @@
|
|||
|
||||
|
||||
function changeLogFormatter(value) {
|
||||
|
||||
var result = '';
|
||||
var pretty_index = '';
|
||||
|
||||
for (var index in value) {
|
||||
result += index + ': <del>' + value[index].old + '</del> <i class="fas fa-long-arrow-alt-right" aria-hidden="true"></i> ' + value[index].new + '<br>'
|
||||
|
||||
|
||||
// Check if it's a custom field
|
||||
if (index.startsWith('_snipeit_')) {
|
||||
pretty_index = index.replace("_snipeit_", "Custom:_");
|
||||
} else {
|
||||
pretty_index = index;
|
||||
}
|
||||
|
||||
extra_pretty_index = prettyLog(pretty_index);
|
||||
|
||||
result += extra_pretty_index + ': <del>' + value[index].old + '</del> <i class="fas fa-long-arrow-alt-right" aria-hidden="true"></i> ' + value[index].new + '<br>'
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function prettyLog(str) {
|
||||
let frags = str.split('_');
|
||||
for (let i = 0; i < frags.length; i++) {
|
||||
frags[i] = frags[i].charAt(0).toUpperCase() + frags[i].slice(1);
|
||||
}
|
||||
return frags.join(' ');
|
||||
}
|
||||
|
||||
|
||||
// Create a linked phone number in the table list
|
||||
function phoneFormatter(value) {
|
||||
|
|
Loading…
Reference in a new issue