diff --git a/.gitignore b/.gitignore index 437be0ca0d..5012c49a8f 100755 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ tests/_support/_generated/* /storage/oauth-public.key *.cache +/public/storage diff --git a/app/Console/Commands/ObjectImportCommand.php b/app/Console/Commands/ObjectImportCommand.php index 6730e6f084..bcb57564d7 100644 --- a/app/Console/Commands/ObjectImportCommand.php +++ b/app/Console/Commands/ObjectImportCommand.php @@ -74,6 +74,7 @@ class ObjectImportCommand extends Command $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) ->setUserId($this->option('user_id')) ->setUpdating($this->option('update')) + ->setShouldNotify($this->option('send-welcome')) ->setUsernameFormat($this->option('username_format')); $logFile = $this->option('logfile'); diff --git a/app/Http/Requests/ItemImportRequest.php b/app/Http/Requests/ItemImportRequest.php index a220ed5c39..b25522e786 100644 --- a/app/Http/Requests/ItemImportRequest.php +++ b/app/Http/Requests/ItemImportRequest.php @@ -50,6 +50,7 @@ class ItemImportRequest extends FormRequest $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) ->setUserId(Auth::id()) ->setUpdating($this->has('import-update')) + ->setShouldNotify($this->has('send-welcome')) ->setUsernameFormat('firstname.lastname') ->setFieldMappings($fieldMappings); // $logFile = storage_path('logs/importer.log'); @@ -60,7 +61,7 @@ class ItemImportRequest extends FormRequest public function log($string) { - // \Log::Info($string); + \Log::Info($string); } public function progress($count) diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index c660038af0..a7c8e61154 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -66,6 +66,9 @@ abstract class Importer 'phone_number' => 'phone number', 'first_name' => 'first name', 'last_name' => 'last name', + 'department' => 'department', + 'manager_first_name' => 'manager first name', + 'manager_last_name' => 'manager last name', ]; /** * Map of item fields->csv names @@ -176,7 +179,6 @@ abstract class Importer { $val = $default; - $key = $this->lookupCustomKey($key); $this->log("Custom Key: ${key}"); @@ -198,7 +200,6 @@ abstract class Importer public function lookupCustomKey($key) { if (array_key_exists($key, $this->fieldMap)) { - // $this->log("Found a match in our custom map: {$key} is " . $this->fieldMap[$key]); return $this->fieldMap[$key]; } // Otherwise no custom key, return original. @@ -307,6 +308,8 @@ abstract class Importer $user->last_name = $user_array['last_name']; $user->username = $user_array['username']; $user->email = $user_array['email']; + $user->manager_id = $user_array['manager_id']; + $user->department_id = $user_array['department_id']; $user->activated = 1; $user->password = $this->tempPassword; @@ -360,6 +363,20 @@ abstract class Importer return $this; } + /** + * Sets the Are we updating items in the import. + * + * @param bool $updating the updating + * + * @return self + */ + public function setShouldNotify($send_welcome) + { + $this->send_welcome = $send_welcome; + + return $this; + } + /** * Defines mappings of csv fields * diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index 9a47948668..701534a464 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -10,6 +10,7 @@ use App\Models\Location; use App\Models\Manufacturer; use App\Models\Statuslabel; use App\Models\Supplier; +use App\Models\Department; use App\Models\User; class ItemImporter extends Importer @@ -54,6 +55,18 @@ class ItemImporter extends Importer if ($this->shouldUpdateField($item_supplier)) { $this->item['supplier_id'] = $this->createOrFetchSupplier($item_supplier); } + + $item_department = $this->findCsvMatch($row, "department"); + if ($this->shouldUpdateField($item_department)) { + $this->item['department_id'] = $this->createOrFetchDepartment($item_department); + } + + $item_manager_first_name = $this->findCsvMatch($row, "manager_first_name"); + $item_manager_last_name = $this->findCsvMatch($row, "manager_last_name"); + if ($this->shouldUpdateField($item_manager_first_name)) { + $this->item['manager_id'] = $this->fetchManager($item_manager_first_name, $item_manager_last_name); + } + $this->item["name"] = $this->findCsvMatch($row, "item_name"); $this->item["notes"] = $this->findCsvMatch($row, "notes"); $this->item["order_number"] = $this->findCsvMatch($row, "order_number"); @@ -84,7 +97,7 @@ class ItemImporter extends Importer */ protected function determineCheckout($row) { - // We only support checkout-to-location for asset, so short circuit otherw. + // We only support checkout-to-location for asset, so short circuit otherwise. if(get_class($this) != AssetImporter::class) { return $this->createOrFetchUser($row); } @@ -161,7 +174,7 @@ class ItemImporter extends Importer * @since 3.0 * @param array * @param $category Category - * @param $manufacturer Manufacturer + * @param $row Manufacturer * @return int Id of asset model created/found * @internal param $asset_modelno string */ @@ -279,6 +292,54 @@ class ItemImporter extends Importer return null; } + /** + * Fetch an existing department, or create new if it doesn't exist + * + * @author A. Gianotto + * @since 4.6.5 + * @param $user_department string + * @return int id of company created/found + */ + public function createOrFetchDepartment($user_department_name) + { + $department = Department::where('name', '=', $user_department_name)->first(); + + if ($department) { + $this->log('A matching Department ' . $user_department_name . ' already exists'); + return $department->id; + } + + $department = new Department(); + $department->name = $user_department_name; + + if ($department->save()) { + $this->log('Department ' . $user_department_name . ' was created'); + return $department->id; + } + $this->logError($department, 'Department'); + return null; + } + + /** + * Fetch an existing manager + * + * @author A. Gianotto + * @since 4.6.5 + * @param $user_manager string + * @return int id of company created/found + */ + public function fetchManager($user_manager_first_name, $user_manager_last_name) + { + $manager = User::where('first_name', '=', $user_manager_first_name) + ->where('last_name', '=', $user_manager_last_name)->first(); + if ($manager) { + $this->log('A matching Manager ' . $user_manager_first_name . ' '. $user_manager_last_name . ' already exists'); + return $manager->id; + } + $this->log('No matching Manager ' . $user_manager_first_name . ' '. $user_manager_last_name . ' found. If their user account is being created through this import, you should re-process this file again. '); + return null; + } + /** * Fetch the existing status label or create new if it doesn't exist. * diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index 019f1d2dce..0f9d29db23 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -12,7 +12,6 @@ class UserImporter extends ItemImporter public function __construct($filename) { parent::__construct($filename); - // $this->users = User::all(); } protected function handle($row) @@ -31,14 +30,18 @@ class UserImporter extends ItemImporter */ public function createUserIfNotExists(array $row) { - // User Specific Bits $this->item['username'] = $this->findCsvMatch($row, 'username'); $this->item['first_name'] = $this->findCsvMatch($row, 'first_name'); $this->item['last_name'] = $this->findCsvMatch($row, 'last_name'); $this->item['email'] = $this->findCsvMatch($row, 'email'); $this->item['phone'] = $this->findCsvMatch($row, 'phone_number'); $this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle'); + $this->item['activated'] = $this->findCsvMatch($row, 'activated'); $this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num'); + $this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department')); + $this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name')); + + $user = User::where('username', $this->item['username'])->first(); if ($user) { if (!$this->updating) { @@ -50,6 +53,7 @@ class UserImporter extends ItemImporter $user->save(); return; } + // This needs to be applied after the update logic, otherwise we'll overwrite user passwords // Issue #5408 $this->item['password'] = $this->tempPassword; @@ -58,7 +62,6 @@ class UserImporter extends ItemImporter $user = new User(); $user->fill($this->sanitizeItemForStoring($user)); if ($user->save()) { - // $user->logCreate('Imported using CSV Importer'); $this->log("User " . $this->item["name"] . ' was created'); if($user->email) { $data = [ @@ -70,7 +73,10 @@ class UserImporter extends ItemImporter ]; // UNCOMMENT this to re-enable sending email notifications on user import - // $user->notify(new WelcomeNotification($data)); + if ($this->send_welcome) { + $user->notify(new WelcomeNotification($data)); + } + } $user = null; $this->item = null; diff --git a/app/Importer/import_mappings.md b/app/Importer/import_mappings.md index f2fff2c2f4..b65a655b21 100644 --- a/app/Importer/import_mappings.md +++ b/app/Importer/import_mappings.md @@ -1,16 +1,20 @@ | CSV | Item | Applicable Types | |---------------------|------------------|-------------------------------------------| +| activated | | User | | asset tag | asset_tag | Asset | | category | category | All | | company | company | All | +| department_id | | User ? All | | item name | item_name | All | -| image | image | asset | +| image | image | Asset | +| email | | | | expiration date | expiration_date | License | | location | location | All | | notes | notes | All | | licensed to email | license_email | License | | licensed to name | license_name | License | | maintained | maintained | License | +| manager_id | | User | | manufacturer | manufacturer | All | | model name | asset_model | Asset | | model number | model_number | Asset | @@ -22,12 +26,12 @@ | reassignable | reassignable | License | | requestable | requestable | Asset, Accessory? | | seats | seats | License | -| serial number | serial | asset, license | -| status | status | asset ? All | +| serial number | serial | Asset, license | +| status | status | Asset ? All | | supplier | supplier | Asset ? All | | termination date | termination_date | License | -| warranty months | warranty_months | asset | +| warranty months | warranty_months | Asset | | User Related Fields | assigned_to | Asset | | name | | | -| email | | | -| username | | | \ No newline at end of file +| username | | | + diff --git a/app/Models/Department.php b/app/Models/Department.php index d9cab7a738..e864afda37 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -25,11 +25,11 @@ class Department extends SnipeModel use ValidatingTrait, UniqueUndeletedTrait; protected $rules = [ - 'name' => 'required|max:255', - 'user_id' => 'required', - 'location_id' => 'numeric|nullable', - 'company_id' => 'numeric|nullable', - 'manager_id' => 'numeric|nullable', + 'name' => 'required|max:255', + 'user_id' => 'nullable|exists:users,id', + 'location_id' => 'numeric|nullable', + 'company_id' => 'numeric|nullable', + 'manager_id' => 'numeric|nullable', ]; /** diff --git a/public/css/build/all.css b/public/css/build/all.css index 418fe416f0..4bfc2811b5 100644 Binary files a/public/css/build/all.css and b/public/css/build/all.css differ diff --git a/public/css/dist/all.css b/public/css/dist/all.css index 418fe416f0..0613103f1f 100644 Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ diff --git a/public/js/build/all.js b/public/js/build/all.js index 7c46019e3f..e08aebc256 100644 Binary files a/public/js/build/all.js and b/public/js/build/all.js differ diff --git a/public/js/build/vue.js b/public/js/build/vue.js index f27c653cc5..deb2cc4b59 100644 Binary files a/public/js/build/vue.js and b/public/js/build/vue.js differ diff --git a/public/js/build/vue.js.map b/public/js/build/vue.js.map index 11085b1750..b8f794cfd5 100644 Binary files a/public/js/build/vue.js.map and b/public/js/build/vue.js.map differ diff --git a/public/js/dist/all.js b/public/js/dist/all.js index 7c46019e3f..4c4e6bef4b 100644 Binary files a/public/js/dist/all.js and b/public/js/dist/all.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 404a4afcad..ff5d568965 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,14 +1,14 @@ { - "/js/build/vue.js": "/js/build/vue.js?id=832c22cb5b66ac81ed06", + "/js/build/vue.js": "/js/build/vue.js?id=94cc644b10b5436da8ef", "/css/AdminLTE.css": "/css/AdminLTE.css?id=5e72463a66acbcc740d5", "/css/app.css": "/css/app.css?id=407edb63cc6b6dc62405", "/css/overrides.css": "/css/overrides.css?id=2d81c3704393bac77011", - "/js/build/vue.js.map": "/js/build/vue.js.map?id=0deaf852882fe2d65263", + "/js/build/vue.js.map": "/js/build/vue.js.map?id=d2f525f3411031bec8a0", "/css/AdminLTE.css.map": "/css/AdminLTE.css.map?id=0be7790b84909dca6a0a", "/css/app.css.map": "/css/app.css.map?id=96b5c985e860716e6a16", "/css/overrides.css.map": "/css/overrides.css.map?id=f7ce9ca49027594ac402", "/css/dist/all.css": "/css/dist/all.css?id=98db4e9b7650453c8b00", - "/js/dist/all.js": "/js/dist/all.js?id=9d02373ef452329336d3", + "/js/dist/all.js": "/js/dist/all.js?id=15c1c83483171165eb54", "/css/build/all.css": "/css/build/all.css?id=98db4e9b7650453c8b00", - "/js/build/all.js": "/js/build/all.js?id=9d02373ef452329336d3" -} \ No newline at end of file + "/js/build/all.js": "/js/build/all.js?id=15c1c83483171165eb54" +} diff --git a/resources/assets/js/components/importer/importer-file.vue b/resources/assets/js/components/importer/importer-file.vue index 7c8dbae002..0bf980e329 100644 --- a/resources/assets/js/components/importer/importer-file.vue +++ b/resources/assets/js/components/importer/importer-file.vue @@ -6,29 +6,50 @@ tr { +
+
+ + +

+
+ +
+ {{ this.statusText }} +
+ + + - - - -
- {{ this.statusText }} -
- @@ -143,6 +168,10 @@ tr { {id: 'jobtitle', text: 'Job Title' }, {id: 'last_name', text: 'Last Name' }, {id: 'phone_number', text: 'Phone Number' }, + {id: 'manager_first_name', text: 'Manager First Name' }, + {id: 'manager_last_name', text: 'Manager Last Name' }, + {id: 'department', text: 'Department' }, + {id: 'activated', text: 'Activated' }, ], customFields: this.customFields, @@ -211,6 +240,7 @@ tr { this.statusText = "Processing..."; this.$http.post(route('api.imports.importFile', this.file.id), { 'import-update': this.options.update, + 'send-welcome': this.options.send_welcome, 'import-type': this.options.importType, 'column-mappings': this.columnMappings }).then( ({body}) => { diff --git a/resources/views/importer/import.blade.php b/resources/views/importer/import.blade.php index b32372833e..6212726e8e 100644 --- a/resources/views/importer/import.blade.php +++ b/resources/views/importer/import.blade.php @@ -67,7 +67,7 @@ @{{ currentFile.filesize }} - +