Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to import asset models (separate from assets) #15802

Merged
merged 42 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5066eb5
Added asset model import to importer
snipe Aug 17, 2024
22c79d8
Squashed commit of the following:
snipe Nov 12, 2024
48713cb
Added additional helper files to assetmodel importer
snipe Nov 12, 2024
615edbb
Updated seeder
snipe Nov 13, 2024
b3f84f7
Added aliases, removed depreciation from import
snipe Nov 13, 2024
d1ba16d
Added requestable to fillable
snipe Nov 13, 2024
6c2cfe0
Removed log line
snipe Nov 13, 2024
6f25ed3
More nuance to human boolean check
snipe Nov 13, 2024
abab062
Added fieldset and depreciation import
snipe Nov 13, 2024
f87ebb4
Fixed tests
snipe Nov 13, 2024
752221e
Removed error log
snipe Nov 13, 2024
503d044
Don’t show new user welcome if location or asset model import
snipe Nov 13, 2024
3d3e3a9
Squashed commit of the following:
snipe Nov 13, 2024
3740b58
Re-added assetmodel import factory
snipe Nov 13, 2024
220902a
Merge branch 'develop' into features/import_models
snipe Nov 13, 2024
4afe873
Fixed tests, updated CSV
snipe Nov 13, 2024
69e3673
Updated CSV
snipe Nov 13, 2024
74ca561
Fixed welcome email toggle
snipe Nov 13, 2024
2f943bc
Better error messaging for bad fields
snipe Nov 13, 2024
813b304
Fixed tests
snipe Nov 13, 2024
85dbbdb
wtf
snipe Nov 13, 2024
66aaafc
Updated variable name
snipe Nov 13, 2024
4d3137e
Added $accessory->created_by = auth()->id();
snipe Nov 13, 2024
0ae1db6
Added created_by
snipe Nov 13, 2024
0a6096e
Updated method name
snipe Nov 13, 2024
d7e5fe5
Use auth facade
snipe Nov 13, 2024
54f7917
Added created by
snipe Nov 13, 2024
8c3b50d
Added created by
snipe Nov 13, 2024
3a1fb61
Added admin sort scope
snipe Nov 13, 2024
b6dbb88
Added created by
snipe Nov 13, 2024
0c3b8cb
Moved created by to create only
snipe Nov 13, 2024
d4018a2
Added created_by to models API
snipe Nov 13, 2024
1aef7ed
Added created by to the import itself
snipe Nov 13, 2024
d3602c0
Allow sorting on admin name in API
snipe Nov 13, 2024
7c3f9ba
Fixed tests
snipe Nov 13, 2024
4a18946
Added created by to imports
snipe Nov 13, 2024
6fee533
Updated CSV
snipe Nov 13, 2024
6f700cc
Added relationship for adminuser
snipe Nov 13, 2024
1caf73f
Fixed casing, fixed typo
snipe Nov 13, 2024
304fddb
Removed unused test
snipe Nov 13, 2024
2e340f9
Two more case fixes
snipe Nov 13, 2024
7ccbc60
Chaneg to ucfirst for Assetmodel -> AssetModel
snipe Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/Http/Controllers/Api/AssetModelsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public function index(Request $request) : JsonResponse | array
'model_number',
'min_amt',
'eol',
'created_by',
'requestable',
'models.notes',
'models.created_at',
Expand All @@ -69,7 +70,7 @@ public function index(Request $request) : JsonResponse | array
'models.deleted_at',
'models.updated_at',
])
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues','adminuser')
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
->withCount('assets as assets_count');

if ($request->input('status')=='deleted') {
Expand All @@ -95,7 +96,7 @@ public function index(Request $request) : JsonResponse | array
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';

switch ($sort) {
switch ($request->input('sort')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not be using the older 'default is models.created_at' sort variable above?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the one above was overriding the sort.

case 'manufacturer':
$assetmodels->OrderManufacturer($order);
break;
Expand All @@ -105,6 +106,9 @@ public function index(Request $request) : JsonResponse | array
case 'fieldset':
$assetmodels->OrderFieldset($order);
break;
case 'created_by':
$assetmodels->OrderByCreatedByName($order);
break;
default:
$assetmodels->orderBy($sort, $order);
break;
Expand Down
8 changes: 5 additions & 3 deletions app/Http/Controllers/Api/ImportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ class ImportController extends Controller
public function index() : JsonResponse | array
{
$this->authorize('import');
$imports = Import::latest()->get();

$imports = Import::with('adminuser')->latest()->get();
return (new ImportsTransformer)->transformImports($imports);
}

Expand Down Expand Up @@ -133,7 +132,7 @@ public function store() : JsonResponse
}

$import->filesize = filesize($path.'/'.$file_name);

$import->created_by = auth()->id();
$import->save();
$results[] = $import;
}
Expand Down Expand Up @@ -177,6 +176,9 @@ public function process(ItemImportRequest $request, $import_id) : JsonResponse
case 'asset':
$redirectTo = 'hardware.index';
break;
case 'assetModel':
$redirectTo = 'models.index';
break;
case 'accessory':
$redirectTo = 'accessories.index';
break;
Expand Down
5 changes: 3 additions & 2 deletions app/Http/Requests/ItemImportRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ public function import(Import $import)

$filename = config('app.private_uploads').'/imports/'.$import->file_path;
$import->import_type = $this->input('import-type');
$class = title_case($import->import_type);
$class = ucfirst($import->import_type);
$classString = "App\\Importer\\{$class}Importer";
$importer = new $classString($filename);
$import->field_map = request('column-mappings');
$import->created_by = auth()->id();
$import->save();
$fieldMappings = [];

Expand All @@ -60,7 +61,7 @@ public function import(Import $import)
$fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER);
}
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
->setUserId(auth()->id())
->setCreatedBy(auth()->id())
->setUpdating($this->get('import-update'))
->setShouldNotify($this->get('send-welcome'))
->setUsernameFormat('firstname.lastname')
Expand Down
4 changes: 4 additions & 0 deletions app/Http/Transformers/AssetModelsTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public function transformAssetModel(AssetModel $assetmodel)
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
'requestable' => ($assetmodel->requestable == '1') ? true : false,
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
'created_by' => ($assetmodel->adminuser) ? [
'id' => (int) $assetmodel->adminuser->id,
'name'=> e($assetmodel->adminuser->present()->fullName()),
] : null,
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'),
Expand Down
1 change: 1 addition & 0 deletions app/Importer/AccessoryImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function createAccessoryIfNotExists($row)
}
$this->log('No Matching Accessory, Creating a new one');
$accessory = new Accessory();
$accessory->created_by = auth()->id();
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
$accessory->fill($this->sanitizeItemForStoring($accessory));
Expand Down
174 changes: 174 additions & 0 deletions app/Importer/AssetModelImporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

namespace App\Importer;

use App\Models\AssetModel;
use App\Models\Depreciation;
use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Log;

/**
* When we are importing users via an Asset/etc import, we use createOrFetchUser() in
* Importer\Importer.php. [ALG]
*
* Class LocationImporter
*/
class AssetModelImporter extends ItemImporter
{
protected $models;

public function __construct($filename)
{
parent::__construct($filename);
}

protected function handle($row)
{
parent::handle($row);
$this->createAssetModelIfNotExists($row);
}

/**
* Create a model if a duplicate does not exist.
* @todo Investigate how this should interact with Importer::createModelIfNotExists
*
* @author A. Gianotto
* @since 6.1.0
* @param array $row
*/
public function createAssetModelIfNotExists(array $row)
{

$editingAssetModel = false;
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'name' alone enough to guarantee uniqueness? I seem to remember us having problems where users had two identically-named models, but with different model numbers, and wanted to maintain those separately? Or I could be misremembering.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yeah, that's a good point...


if ($assetModel) {
if (! $this->updating) {
$this->log('A matching Model '.$this->item['name'].' already exists');
return;
}

$this->log('Updating Model');
$editingAssetModel = true;
} else {
$this->log('No Matching Model, Create a new one');
$assetModel = new AssetModel();
}

// Pull the records from the CSV to determine their values
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
$this->item['category'] = trim($this->findCsvMatch($row, 'category'));
$this->item['manufacturer'] = trim($this->findCsvMatch($row, 'manufacturer'));
$this->item['min_amt'] = trim($this->findCsvMatch($row, 'min_amt'));
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
$this->item['eol'] = trim($this->findCsvMatch($row, 'eol'));
$this->item['notes'] = trim($this->findCsvMatch($row, 'notes'));
$this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset'));
$this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation'));
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0;

if (!empty($this->item['category'])) {
if ($category = $this->createOrFetchCategory($this->item['category'])) {
$this->item['category_id'] = $category;
}
}
if (!empty($this->item['manufacturer'])) {
if ($manufacturer = $this->createOrFetchManufacturer($this->item['manufacturer'])) {
$this->item['manufacturer_id'] = $manufacturer;
}
}

if (!empty($this->item['depreciation'])) {
if ($depreciation = $this->fetchDepreciation($this->item['depreciation'])) {
$this->item['depreciation_id'] = $depreciation;
}
}

if (!empty($this->item['fieldset'])) {
if ($fieldset = $this->createOrFetchCustomFieldset($this->item['fieldset'])) {
$this->item['fieldset_id'] = $fieldset;
}
}

Log::debug('Item array is: ');
Log::debug(print_r($this->item, true));


if ($editingAssetModel) {
Log::debug('Updating existing model');
$assetModel->update($this->sanitizeItemForUpdating($assetModel));
} else {
Log::debug('Creating model');
$assetModel->fill($this->sanitizeItemForStoring($assetModel));
$assetModel->created_by = auth()->id();
}

if ($assetModel->save()) {
$this->log('AssetModel '.$assetModel->name.' created or updated from CSV import');
return $assetModel;

} else {
$this->log($assetModel->getErrors()->first());
$this->addErrorToBag($assetModel, $assetModel->getErrors()->keys()[0], $assetModel->getErrors()->first());
return $assetModel->getErrors();
}

}


/**
* Fetch an existing depreciation, or create new if it doesn't exist.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't create a new one if it doesn't exist, this comment should probably change.

*
* We only do a fetch vs create here since Depreciations have additional fields required
* and cannot be created without them (months, for example.))
*
* @author A. Gianotto
* @since 7.1.3
* @param $depreciation_name string
* @return int id of depreciation created/found
*/
public function fetchDepreciation($depreciation_name) : ?int
{
if ($depreciation_name != '') {

if ($depreciation = Depreciation::where('name', '=', $depreciation_name)->first()) {
$this->log('A matching Depreciation '.$depreciation_name.' already exists');
return $depreciation->id;
}
}

return null;
}

/**
* Fetch an existing fieldset, or create new if it doesn't exist
*
* @author A. Gianotto
* @since 7.1.3
* @param $fieldset_name string
* @return int id of fieldset created/found
*/
public function createOrFetchCustomFieldset($fieldset_name) : ?int
{
if ($fieldset_name != '') {
$fieldset = CustomFieldset::where('name', '=', $fieldset_name)->first();

if ($fieldset) {
$this->log('A matching fieldset '.$fieldset_name.' already exists');
return $fieldset->id;
}

$fieldset = new CustomFieldset();
$fieldset->name = $fieldset_name;

if ($fieldset->save()) {
$this->log('Fieldset '.$fieldset_name.' was created');

return $fieldset->id;
}
$this->logError($fieldset, 'Fieldset');
}

return null;
}
}
3 changes: 2 additions & 1 deletion app/Importer/ComponentImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function createComponentIfNotExists()
}
$this->log('No matching component, creating one');
$component = new Component;
$component->created_by = auth()->id();
$component->fill($this->sanitizeItemForStoring($component));

// This sets an attribute on the Loggable trait for the action log
Expand All @@ -58,7 +59,7 @@ public function createComponentIfNotExists()
if (isset($this->item['asset_tag']) && ($asset = Asset::where('asset_tag', $this->item['asset_tag'])->first())) {
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'created_by' => $this->created_by,
'created_by' => auth()->id(),
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => 1, // Only assign the first one to the asset
'asset_id' => $asset->id,
Expand Down
1 change: 1 addition & 0 deletions app/Importer/ConsumableImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function createConsumableIfNotExists($row)
}
$this->log('No matching consumable, creating one');
$consumable = new Consumable();
$consumable->created_by = auth()->id();
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
Expand Down
14 changes: 13 additions & 1 deletion app/Importer/Importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ protected function createOrFetchUser($row, $type = 'user')

// No luck finding a user on username or first name, let's create one.
$user = new User;

$user->first_name = $user_array['first_name'];
$user->last_name = $user_array['last_name'];
$user->username = $user_array['username'];
Expand Down Expand Up @@ -406,7 +407,7 @@ protected function findUserByNumber($user_name)
*
* @return self
*/
public function setUserId($created_by)
public function setCreatedBy($created_by)
{
$this->created_by = $created_by;

Expand Down Expand Up @@ -492,6 +493,16 @@ public function setUsernameFormat($usernameFormat)

public function fetchHumanBoolean($value)
{
$true = [
'yes',
'y',
'true',
];

if (in_array(strtolower($value), $true)) {
return 1;
}

return (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
}

Expand Down Expand Up @@ -528,6 +539,7 @@ public function createOrFetchDepartment($user_department_name)
return null;
}


/**
* Fetch an existing manager
*
Expand Down
Loading
Loading