mirror of
https://github.com/snipe/snipe-it.git
synced 2024-11-09 23:24:06 -08:00
Merge pull request #15322 from snipe/importer_model_fixes
Importer model fixes
This commit is contained in:
commit
e7ef3bf515
|
@ -11,15 +11,17 @@ trait TwoColumnUniqueUndeletedTrait
|
||||||
* @param string $field
|
* @param string $field
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function prepareTwoColumnUniqueUndeletedRule($parameters, $field)
|
protected function prepareTwoColumnUniqueUndeletedRule($parameters)
|
||||||
{
|
{
|
||||||
$column = $parameters[0];
|
$column = $parameters[0];
|
||||||
$value = $this->{$parameters[0]};
|
$value = $this->{$parameters[0]};
|
||||||
|
|
||||||
|
// This is an existing model we're updating so ignore the current ID ($this->getKey())
|
||||||
if ($this->exists) {
|
if ($this->exists) {
|
||||||
return 'two_column_unique_undeleted:'.$this->table.','.$this->getKey().','.$column.','.$value;
|
return 'two_column_unique_undeleted:'.$this->table.','.$this->getKey().','.$column.','.$value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a new record, so we can ignore the current ID
|
||||||
return 'two_column_unique_undeleted:'.$this->table.',0,'.$column.','.$value;
|
return 'two_column_unique_undeleted:'.$this->table.',0,'.$column.','.$value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,10 @@ class AssetImporter extends ItemImporter
|
||||||
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
|
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
|
||||||
if ($asset) {
|
if ($asset) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching Asset '.$asset_tag.' already exists');
|
$exists_error = trans('general.import_asset_tag_exists', ['asset_tag' => $asset_tag]);
|
||||||
return;
|
$this->log($exists_error);
|
||||||
|
$this->addErrorToBag($asset, 'asset_tag', $exists_error);
|
||||||
|
return $exists_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log('Updating Asset');
|
$this->log('Updating Asset');
|
||||||
|
|
|
@ -281,6 +281,13 @@ abstract class Importer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function addErrorToBag($item, $field, $error_message)
|
||||||
|
{
|
||||||
|
if ($this->errorCallback) {
|
||||||
|
call_user_func($this->errorCallback, $item, $field, [$field => [$error_message]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the user matching given data, or creates a new one if there is no match.
|
* Finds the user matching given data, or creates a new one if there is no match.
|
||||||
* This is NOT used by the User Import, only for Asset/Accessory/etc where
|
* This is NOT used by the User Import, only for Asset/Accessory/etc where
|
||||||
|
|
|
@ -196,64 +196,77 @@ class ItemImporter extends Importer
|
||||||
{
|
{
|
||||||
$condition = array();
|
$condition = array();
|
||||||
$asset_model_name = $this->findCsvMatch($row, 'asset_model');
|
$asset_model_name = $this->findCsvMatch($row, 'asset_model');
|
||||||
|
$asset_model_category = $this->findCsvMatch($row, 'category');
|
||||||
$asset_modelNumber = $this->findCsvMatch($row, 'model_number');
|
$asset_modelNumber = $this->findCsvMatch($row, 'model_number');
|
||||||
|
|
||||||
// TODO: At the moment, this means we can't update the model number if the model name stays the same.
|
// TODO: At the moment, this means we can't update the model number if the model name stays the same.
|
||||||
if (! $this->shouldUpdateField($asset_model_name)) {
|
if (! $this->shouldUpdateField($asset_model_name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((empty($asset_model_name)) && (! empty($asset_modelNumber))) {
|
if ((empty($asset_model_name)) && (! empty($asset_modelNumber))) {
|
||||||
$asset_model_name = $asset_modelNumber;
|
$asset_model_name = $asset_modelNumber;
|
||||||
} elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
} elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
||||||
$asset_model_name = 'Unknown';
|
$asset_model_name = 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
$asset_model = AssetModel::select('id');
|
||||||
$condition[] = ['name', '=', $asset_model_name];
|
|
||||||
} elseif ((!empty($asset_model_name)) && (!empty($asset_modelNumber))) {
|
if (!empty($asset_model_name)) {
|
||||||
$condition[] = ['name', '=', $asset_model_name];
|
$asset_model = $asset_model->where('name', '=', $asset_model_name);
|
||||||
$condition[] = ['model_number', '=', $asset_modelNumber];
|
|
||||||
|
if (!empty($asset_modelNumber)) {
|
||||||
|
$asset_model = $asset_model->where('model_number', '=', $asset_modelNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$editingModel = $this->updating;
|
$editingModel = $this->updating;
|
||||||
$asset_model = AssetModel::where($condition)->first();
|
$asset_model = $asset_model->first();
|
||||||
|
|
||||||
if ($asset_model) {
|
if ($asset_model) {
|
||||||
|
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching model already exists, returning it.');
|
$this->log('A matching model already exists, returning it.');
|
||||||
|
|
||||||
return $asset_model->id;
|
return $asset_model->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log('Matching Model found, updating it.');
|
$this->log('Matching Model found, updating it.');
|
||||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||||
$item['name'] = $asset_model_name;
|
$item['name'] = $asset_model_name;
|
||||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||||
|
|
||||||
if(!empty($asset_modelNumber)){
|
if (!empty($asset_modelNumber)){
|
||||||
$item['model_number'] = $asset_modelNumber;
|
$item['model_number'] = $asset_modelNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset_model->update($item);
|
$asset_model->update($item);
|
||||||
$asset_model->save();
|
$asset_model->save();
|
||||||
$this->log('Asset Model Updated');
|
$this->log('Asset Model Updated');
|
||||||
|
|
||||||
return $asset_model->id;
|
return $asset_model->id;
|
||||||
}
|
|
||||||
$this->log('No Matching Model, Creating a new one');
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log('No Matching Model, Creating a new one');
|
||||||
$asset_model = new AssetModel();
|
$asset_model = new AssetModel();
|
||||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||||
$item['name'] = $asset_model_name;
|
$item['name'] = $asset_model_name;
|
||||||
$item['model_number'] = $asset_modelNumber;
|
$item['model_number'] = $asset_modelNumber;
|
||||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||||
|
$item['category_id'] = $this->createOrFetchCategory($asset_model_category);
|
||||||
|
|
||||||
$asset_model->fill($item);
|
$asset_model->fill($item);
|
||||||
|
//$asset_model = AssetModel::firstOrNew($item);
|
||||||
$item = null;
|
$item = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($asset_model->save()) {
|
if ($asset_model->save()) {
|
||||||
$this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created');
|
$this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created');
|
||||||
|
|
||||||
return $asset_model->id;
|
return $asset_model->id;
|
||||||
}
|
}
|
||||||
|
$this->log('Asset Model Errors: '.$asset_model->getErrors());
|
||||||
$this->logError($asset_model, 'Asset Model "'.$asset_model_name.'"');
|
$this->logError($asset_model, 'Asset Model "'.$asset_model_name.'"');
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -374,6 +374,12 @@ class Importer extends Component
|
||||||
'model name',
|
'model name',
|
||||||
'model',
|
'model',
|
||||||
],
|
],
|
||||||
|
'eol_date' =>
|
||||||
|
[
|
||||||
|
'eol',
|
||||||
|
'eol date',
|
||||||
|
'asset eol date',
|
||||||
|
],
|
||||||
'gravatar' =>
|
'gravatar' =>
|
||||||
[
|
[
|
||||||
'gravatar',
|
'gravatar',
|
||||||
|
|
|
@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Watson\Validating\ValidatingTrait;
|
use Watson\Validating\ValidatingTrait;
|
||||||
use \App\Presenters\AssetModelPresenter;
|
use \App\Presenters\AssetModelPresenter;
|
||||||
|
use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for Asset Models. Asset Models contain higher level
|
* Model for Asset Models. Asset Models contain higher level
|
||||||
|
@ -21,21 +22,8 @@ class AssetModel extends SnipeModel
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
protected $presenter = AssetModelPresenter::class;
|
|
||||||
use Loggable, Requestable, Presentable;
|
use Loggable, Requestable, Presentable;
|
||||||
|
use TwoColumnUniqueUndeletedTrait;
|
||||||
protected $table = 'models';
|
|
||||||
protected $hidden = ['user_id', 'deleted_at'];
|
|
||||||
|
|
||||||
// Declare the rules for the model validation
|
|
||||||
protected $rules = [
|
|
||||||
'name' => 'string|required|min:1|max:255|unique:models,name',
|
|
||||||
'model_number' => 'string|max:255|nullable',
|
|
||||||
'min_amt' => 'integer|min:0|nullable',
|
|
||||||
'category_id' => 'required|integer|exists:categories,id',
|
|
||||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
|
||||||
'eol' => 'integer:min:0|max:240|nullable',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the model should inject its identifier to the unique
|
* Whether the model should inject its identifier to the unique
|
||||||
|
@ -44,8 +32,26 @@ class AssetModel extends SnipeModel
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected $injectUniqueIdentifier = true;
|
protected $injectUniqueIdentifier = true;
|
||||||
use ValidatingTrait;
|
use ValidatingTrait;
|
||||||
|
protected $table = 'models';
|
||||||
|
protected $hidden = ['user_id', 'deleted_at'];
|
||||||
|
protected $presenter = AssetModelPresenter::class;
|
||||||
|
|
||||||
|
// Declare the rules for the model validation
|
||||||
|
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'name' => 'string|required|min:1|max:255|two_column_unique_undeleted:model_number',
|
||||||
|
'model_number' => 'string|max:255|nullable|two_column_unique_undeleted:name',
|
||||||
|
'min_amt' => 'integer|min:0|nullable',
|
||||||
|
'category_id' => 'required|integer|exists:categories,id',
|
||||||
|
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||||
|
'eol' => 'integer:min:0|max:240|nullable',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
|
@ -86,6 +92,9 @@ class AssetModel extends SnipeModel
|
||||||
'manufacturer' => ['name'],
|
'manufacturer' => ['name'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes the model -> assets relationship
|
* Establishes the model -> assets relationship
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,10 +6,7 @@ use App\Models\CustomField;
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,18 +88,26 @@ class ValidationServiceProvider extends ServiceProvider
|
||||||
*
|
*
|
||||||
* $parameters[0] - the name of the first table we're looking at
|
* $parameters[0] - the name of the first table we're looking at
|
||||||
* $parameters[1] - the ID (this will be 0 on new creations)
|
* $parameters[1] - the ID (this will be 0 on new creations)
|
||||||
* $parameters[2] - the name of the second table we're looking at
|
* $parameters[2] - the name of the second field we're looking at
|
||||||
* $parameters[3] - the value that the request is passing for the second table we're
|
* $parameters[3] - the value that the request is passing for the second table we're
|
||||||
* checking for uniqueness across
|
* checking for uniqueness across
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
|
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
|
||||||
|
|
||||||
if (count($parameters)) {
|
if (count($parameters)) {
|
||||||
|
|
||||||
$count = DB::table($parameters[0])
|
$count = DB::table($parameters[0])
|
||||||
->select('id')->where($attribute, '=', $value)
|
->select('id')
|
||||||
->whereNull('deleted_at')
|
->where($attribute, '=', $value)
|
||||||
->where('id', '!=', $parameters[1])
|
->where('id', '!=', $parameters[1]);
|
||||||
->where($parameters[2], $parameters[3])->count();
|
|
||||||
|
if ($parameters[3]!='') {
|
||||||
|
$count = $count->where($parameters[2], $parameters[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = $count->whereNull('deleted_at')
|
||||||
|
->count();
|
||||||
|
|
||||||
return $count < 1;
|
return $count < 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -561,5 +561,6 @@ return [
|
||||||
'remaining_var' => ':count Remaining',
|
'remaining_var' => ':count Remaining',
|
||||||
'assets_in_var' => 'Assets in :name :type',
|
'assets_in_var' => 'Assets in :name :type',
|
||||||
'label' => 'Label',
|
'label' => 'Label',
|
||||||
|
'import_asset_tag_exists' => 'An asset with the asset tag :asset_tag already exists and an update was not requested. No change was made.',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<table class="table table-striped table-bordered" id="errors-table">
|
<table class="table table-striped table-bordered" id="errors-table">
|
||||||
<thead>
|
<thead>
|
||||||
<th>{{ trans('general.item') }}</th>
|
<th>{{ trans('general.item') }}</th>
|
||||||
|
<th>Field</th>
|
||||||
<th>{{ trans('general.error') }}</th>
|
<th>{{ trans('general.error') }}</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -41,8 +42,8 @@
|
||||||
@foreach($error_bag as $field => $error_list)
|
@foreach($error_bag as $field => $error_list)
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>{{ $key }}</b></td>
|
<td><b>{{ $key }}</b></td>
|
||||||
|
<td><b>{{ $field }}</b></td>
|
||||||
<td>
|
<td>
|
||||||
<b>{{ $field }}:</b>
|
|
||||||
<span>{{ implode(", ",$error_list) }}</span>
|
<span>{{ implode(", ",$error_list) }}</span>
|
||||||
<br />
|
<br />
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -24,7 +24,7 @@ class CreateAssetModelsTest extends TestCase
|
||||||
$response = $this->actingAsForApi(User::factory()->superuser()->create())
|
$response = $this->actingAsForApi(User::factory()->superuser()->create())
|
||||||
->postJson(route('api.models.store'), [
|
->postJson(route('api.models.store'), [
|
||||||
'name' => 'Test AssetModel',
|
'name' => 'Test AssetModel',
|
||||||
'category_id' => Category::factory()->create()->id
|
'category_id' => Category::factory()->assetLaptopCategory()->create()->id
|
||||||
])
|
])
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertStatusMessageIs('success')
|
->assertStatusMessageIs('success')
|
||||||
|
@ -53,9 +53,52 @@ class CreateAssetModelsTest extends TestCase
|
||||||
])
|
])
|
||||||
->json();
|
->json();
|
||||||
|
|
||||||
// dd($response);
|
|
||||||
$this->assertFalse(AssetModel::where('name', 'Test AssetModel')->exists());
|
$this->assertFalse(AssetModel::where('name', 'Test AssetModel')->exists());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUniquenessAcrossModelNameAndModelNumber()
|
||||||
|
{
|
||||||
|
AssetModel::factory()->create(['name' => 'Test Model', 'model_number'=>'1234']);
|
||||||
|
|
||||||
|
$this->actingAsForApi(User::factory()->superuser()->create())
|
||||||
|
->postJson(route('api.models.store'), [
|
||||||
|
'name' => 'Test Model',
|
||||||
|
'model_number' => '1234',
|
||||||
|
'category_id' => Category::factory()->assetLaptopCategory()->create()->id
|
||||||
|
])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertOk()
|
||||||
|
->assertStatusMessageIs('error')
|
||||||
|
->assertJson([
|
||||||
|
'messages' => [
|
||||||
|
'name' => ['The name must be unique across models and model number. '],
|
||||||
|
'model_number' => ['The model number must be unique across models and name. '],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->json();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUniquenessAcrossModelNameAndModelNumberWithBlankModelNumber()
|
||||||
|
{
|
||||||
|
AssetModel::factory()->create(['name' => 'Test Model']);
|
||||||
|
|
||||||
|
$this->actingAsForApi(User::factory()->superuser()->create())
|
||||||
|
->postJson(route('api.models.store'), [
|
||||||
|
'name' => 'Test Model',
|
||||||
|
'category_id' => Category::factory()->assetLaptopCategory()->create()->id
|
||||||
|
])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertOk()
|
||||||
|
->assertStatusMessageIs('error')
|
||||||
|
->assertJson([
|
||||||
|
'messages' => [
|
||||||
|
'name' => ['The name must be unique across models and model number. '],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->json();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ class CreateAssetModelsTest extends TestCase
|
||||||
$this->assertFalse(AssetModel::where('name', 'Test Model')->exists());
|
$this->assertFalse(AssetModel::where('name', 'Test Model')->exists());
|
||||||
|
|
||||||
$this->actingAs(User::factory()->superuser()->create())
|
$this->actingAs(User::factory()->superuser()->create())
|
||||||
|
->from(route('models.create'))
|
||||||
->post(route('models.store'), [
|
->post(route('models.store'), [
|
||||||
'name' => 'Test Model',
|
'name' => 'Test Model',
|
||||||
'category_id' => Category::factory()->create()->id
|
'category_id' => Category::factory()->create()->id
|
||||||
|
@ -51,4 +52,46 @@ class CreateAssetModelsTest extends TestCase
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUniquenessAcrossModelNameAndModelNumber()
|
||||||
|
{
|
||||||
|
|
||||||
|
AssetModel::factory()->create(['name' => 'Test Model', 'model_number'=>'1234']);
|
||||||
|
|
||||||
|
$response = $this->actingAs(User::factory()->superuser()->create())
|
||||||
|
->from(route('models.create'))
|
||||||
|
->post(route('models.store'), [
|
||||||
|
'name' => 'Test Model',
|
||||||
|
'model_number' => '1234',
|
||||||
|
'category_id' => Category::factory()->create()->id
|
||||||
|
])
|
||||||
|
->assertStatus(302)
|
||||||
|
->assertSessionHasErrors(['name','model_number'])
|
||||||
|
->assertRedirect(route('models.create'))
|
||||||
|
->assertInvalid(['name','model_number']);
|
||||||
|
|
||||||
|
$this->followRedirects($response)->assertSee(trans('general.error'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUniquenessAcrossModelNameAndModelNumberWithoutModelNumber()
|
||||||
|
{
|
||||||
|
|
||||||
|
AssetModel::factory()->create(['name' => 'Test Model', 'model_number'=> null]);
|
||||||
|
|
||||||
|
$response = $this->actingAs(User::factory()->superuser()->create())
|
||||||
|
->from(route('models.create'))
|
||||||
|
->post(route('models.store'), [
|
||||||
|
'name' => 'Test Model',
|
||||||
|
'model_number' => null,
|
||||||
|
'category_id' => Category::factory()->create()->id
|
||||||
|
])
|
||||||
|
->assertStatus(302)
|
||||||
|
->assertSessionHasErrors(['name'])
|
||||||
|
->assertRedirect(route('models.create'))
|
||||||
|
->assertInvalid(['name']);
|
||||||
|
|
||||||
|
$this->followRedirects($response)->assertSee(trans('general.error'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue