Features/add manager and dept to importer (#6277)

* Ignore the simlink for public storage

* Added manager and department to user import

* More UI importer tweaks

* Fisxed typos
This commit is contained in:
snipe 2018-10-02 15:43:54 -07:00 committed by GitHub
parent 10bc35d604
commit c8bff3ef38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 8803 additions and 99 deletions

1
.gitignore vendored
View file

@ -50,3 +50,4 @@ tests/_support/_generated/*
/storage/oauth-public.key
*.cache
/public/storage

View file

@ -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');

View file

@ -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)

View file

@ -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
*

View file

@ -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.
*

View file

@ -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;

View file

@ -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 | | |
| username | | |

View file

@ -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',
];
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

40
public/js/dist/all.js vendored

File diff suppressed because one or more lines are too long

View file

@ -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"
}
"/js/build/all.js": "/js/build/all.js?id=15c1c83483171165eb54"
}

View file

@ -6,29 +6,50 @@ tr {
<template>
<tr v-show="processDetail">
<td colspan="3">
<td colspan="5">
<div class="col-md-2 text-left">
</div>
<div class="col-md-8 col-md-offset-2 text-center" style="padding-top: 30px; margin: 0 auto;">
<div class="col-md-12 text-left">
<h4 class="modal-title">Import File:</h4>
<div class="dynamic-form-row">
<div class="col-md-4 col-xs-12">
<div class="col-md-5 col-xs-12">
<label for="import-type">Import Type:</label>
</div>
<div class="col-md-4 col-xs-12">
<div class="col-md-7 col-xs-12">
<select2 :options="options.importTypes" v-model="options.importType" required>
<option disabled value="0"></option>
</select2>
</div>
</div>
<div class="dynamic-form-row">
<div class="col-md-4 col-xs-12">
<div class="col-md-5 col-xs-12">
<label for="import-update">Update Existing Values?:</label>
</div>
<div class="col-md-4 col-xs-12">
<div class="col-md-7 col-xs-12">
<input type="checkbox" name="import-update" v-model="options.update">
</div>
</div>
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="send-welcome">Send Welcome Email for new Users?</label>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" name="send-welcome" v-model="options.send_welcome">
</div>
</div>
</div>
<div class="alert col-md-12"
:class="alertClass"
style="text-align:left"
v-if="statusText">
{{ this.statusText }}
</div>
<div class="col-md-12" style="padding-top: 30px;">
<table class="table">
<div class="text-left" style="padding-top: 30px;">
<table class="table table-striped snipe-table">
<thead>
<th>Header Field</th>
<th>Import Field</th>
@ -54,21 +75,25 @@ tr {
</template>
</tbody>
</table>
<br>
<div class="col-md-8 col-md-offset-2 text-right">
<button type="button" class="btn btn-sm btn-default" @click="processDetail = false">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary" @click="postSave">Import</button>
<br><br>
</div>
<div class="alert col-md-12" style="padding-top: 20px;"
:class="alertClass"
style="text-align:left"
v-if="statusText">
{{ this.statusText }}
</div>
</div>
</div>
</td>
<td>
<button type="button" class="btn btn-sm btn-default" @click="processDetail = false">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary" @click="postSave">Import</button>
<div
class="alert col-md-5 col-md-offset-1"
:class="alertClass"
style="text-align:left"
v-if="statusText"
>
{{ this.statusText }}
</div>
</td>
</tr>
</template>
@ -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}) => {

View file

@ -67,7 +67,7 @@
<td>@{{ currentFile.filesize }}</td>
<td>
<button class="btn btn-sm btn-info" @click="toggleEvent(currentFile.id)">Process</button>
<button class="btn btn-sm btn-danger" @click="deleteFile(currentFile)"><i class="fa fa-trash icon-white"></i></button>
<button class="btn btn-sm btn-danger" @click="deleteFile(currentFile)"><i class="fa fa-trash icon-white"></i></button>
</td>
</tr>
<import-file