Port import manager code to develop

This commit is contained in:
snipe 2018-10-03 00:52:29 -07:00
parent 8937edb97e
commit c8ad45b11e
14 changed files with 177 additions and 87 deletions

View file

@ -74,6 +74,7 @@ class ObjectImportCommand extends Command
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
->setUserId($this->option('user_id')) ->setUserId($this->option('user_id'))
->setUpdating($this->option('update')) ->setUpdating($this->option('update'))
->setShouldNotify($this->option('send-welcome'))
->setUsernameFormat($this->option('username_format')); ->setUsernameFormat($this->option('username_format'));
$logFile = $this->option('logfile'); $logFile = $this->option('logfile');

View file

@ -50,6 +50,7 @@ class ItemImportRequest extends FormRequest
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
->setUserId(Auth::id()) ->setUserId(Auth::id())
->setUpdating($this->has('import-update')) ->setUpdating($this->has('import-update'))
->setShouldNotify($this->has('send-welcome'))
->setUsernameFormat('firstname.lastname') ->setUsernameFormat('firstname.lastname')
->setFieldMappings($fieldMappings); ->setFieldMappings($fieldMappings);
// $logFile = storage_path('logs/importer.log'); // $logFile = storage_path('logs/importer.log');

View file

@ -66,7 +66,9 @@ abstract class Importer
'phone_number' => 'phone number', 'phone_number' => 'phone number',
'first_name' => 'first name', 'first_name' => 'first name',
'last_name' => 'last name', 'last_name' => 'last name',
'department' => 'department' 'department' => 'department',
'manager_first_name' => 'manager last name',
'manager_last_name' => 'manager last name',
]; ];
/** /**
* Map of item fields->csv names * Map of item fields->csv names
@ -207,7 +209,6 @@ abstract class Importer
public function lookupCustomKey($key) public function lookupCustomKey($key)
{ {
if (array_key_exists($key, $this->fieldMap)) { 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]; return $this->fieldMap[$key];
} }
// Otherwise no custom key, return original. // Otherwise no custom key, return original.
@ -323,6 +324,8 @@ abstract class Importer
$user->last_name = $user_array['last_name']; $user->last_name = $user_array['last_name'];
$user->username = $user_array['username']; $user->username = $user_array['username'];
$user->email = $user_array['email']; $user->email = $user_array['email'];
$user->manager_id = $user_array['manager_id'];
$user->department_id = $user_array['department_id'];
$user->activated = 1; $user->activated = 1;
$user->password = $this->tempPassword; $user->password = $this->tempPassword;
@ -376,6 +379,20 @@ abstract class Importer
return $this; return $this;
} }
/**
* Sets whether or not we should notify the user with a welcome email
*
* @param bool $send_welcome the send-welcome flag
*
* @return self
*/
public function setShouldNotify($send_welcome)
{
$this->send_welcome = $send_welcome;
return $this;
}
/** /**
* Defines mappings of csv fields * Defines mappings of csv fields
* *

View file

@ -10,6 +10,7 @@ use App\Models\Location;
use App\Models\Manufacturer; use App\Models\Manufacturer;
use App\Models\Statuslabel; use App\Models\Statuslabel;
use App\Models\Supplier; use App\Models\Supplier;
use App\Models\Department;
use App\Models\User; use App\Models\User;
class ItemImporter extends Importer class ItemImporter extends Importer
@ -54,6 +55,19 @@ class ItemImporter extends Importer
if ($this->shouldUpdateField($item_supplier)) { if ($this->shouldUpdateField($item_supplier)) {
$this->item['supplier_id'] = $this->createOrFetchSupplier($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, "manage_first_name");
$item_manager_last_name = $this->findCsvMatch($row, "manage_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["name"] = $this->findCsvMatch($row, "item_name");
$this->item["notes"] = $this->findCsvMatch($row, "notes"); $this->item["notes"] = $this->findCsvMatch($row, "notes");
$this->item["order_number"] = $this->findCsvMatch($row, "order_number"); $this->item["order_number"] = $this->findCsvMatch($row, "order_number");
@ -84,7 +98,7 @@ class ItemImporter extends Importer
*/ */
protected function determineCheckout($row) 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) { if(get_class($this) != AssetImporter::class) {
return $this->createOrFetchUser($row); return $this->createOrFetchUser($row);
} }
@ -160,8 +174,7 @@ class ItemImporter extends Importer
* @author Daniel Melzter * @author Daniel Melzter
* @since 3.0 * @since 3.0
* @param array * @param array
* @param $category Category * @param $row Row
* @param $manufacturer Manufacturer
* @return int Id of asset model created/found * @return int Id of asset model created/found
* @internal param $asset_modelno string * @internal param $asset_modelno string
*/ */
@ -279,6 +292,27 @@ class ItemImporter extends Importer
return null; 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. * Fetch the existing status label or create new if it doesn't exist.
* *

View file

@ -13,7 +13,6 @@ class UserImporter extends ItemImporter
public function __construct($filename) public function __construct($filename)
{ {
parent::__construct($filename); parent::__construct($filename);
// $this->users = User::all();
} }
protected function handle($row) protected function handle($row)
@ -39,7 +38,11 @@ class UserImporter extends ItemImporter
$this->item['email'] = $this->findCsvMatch($row, 'email'); $this->item['email'] = $this->findCsvMatch($row, 'email');
$this->item['phone'] = $this->findCsvMatch($row, 'phone_number'); $this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
$this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle'); $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['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_department = $this->findCsvMatch($row, 'department'); $user_department = $this->findCsvMatch($row, 'department');
if ($this->shouldUpdateField($user_department)) { if ($this->shouldUpdateField($user_department)) {
$this->item["department_id"] = $this->createOrFetchDepartment($user_department); $this->item["department_id"] = $this->createOrFetchDepartment($user_department);
@ -73,7 +76,9 @@ class UserImporter extends ItemImporter
'last_name' => $user->last_name, 'last_name' => $user->last_name,
'password' => $this->tempPassword, 'password' => $this->tempPassword,
]; ];
$user->notify(new WelcomeNotification($data)); if ($this->send_welcome) {
$user->notify(new WelcomeNotification($data));
}
} }
$user = null; $user = null;
$this->item = null; $this->item = null;

View file

@ -1,16 +1,19 @@
| CSV | Item | Applicable Types | | CSV | Item | Applicable Types |
|---------------------|------------------|-------------------------------------------| |---------------------|------------------|-------------------------------------------|
| activated | | User |
| asset tag | asset_tag | Asset | | asset tag | asset_tag | Asset |
| category | category | All | | category | category | All |
| company | company | All | | company | company | All |
| item name | item_name | All | | item name | item_name | All |
| image | image | asset | | image | image | Asset |
| department_id | | User ? All |
| expiration date | expiration_date | License | | expiration date | expiration_date | License |
| location | location | All | | location | location | All |
| notes | notes | All | | notes | notes | All |
| licensed to email | license_email | License | | licensed to email | license_email | License |
| licensed to name | license_name | License | | licensed to name | license_name | License |
| maintained | maintained | License | | maintained | maintained | License |
| manager_id | | User |
| manufacturer | manufacturer | All | | manufacturer | manufacturer | All |
| model name | asset_model | Asset | | model name | asset_model | Asset |
| model number | model_number | Asset | | model number | model_number | Asset |
@ -22,12 +25,12 @@
| reassignable | reassignable | License | | reassignable | reassignable | License |
| requestable | requestable | Asset, Accessory? | | requestable | requestable | Asset, Accessory? |
| seats | seats | License | | seats | seats | License |
| serial number | serial | asset, license | | serial number | serial | Asset, license |
| status | status | asset ? All | | status | status | Asset ? All |
| supplier | supplier | Asset ? All | | supplier | supplier | Asset ? All |
| termination date | termination_date | License | | termination date | termination_date | License |
| warranty months | warranty_months | asset | | warranty months | warranty_months | Asset |
| User Related Fields | assigned_to | Asset | | User Related Fields | assigned_to | Asset |
| name | | | | name | | |
| email | | | | email | | |
| username | | | | username | | |

View file

@ -25,11 +25,11 @@ class Department extends SnipeModel
use ValidatingTrait, UniqueUndeletedTrait; use ValidatingTrait, UniqueUndeletedTrait;
protected $rules = [ protected $rules = [
'name' => 'required|max:255', 'name' => 'required|max:255',
'user_id' => 'required', 'user_id' => 'nullable|exists:users,id',
'location_id' => 'numeric|nullable', 'location_id' => 'numeric|nullable',
'company_id' => 'numeric|nullable', 'company_id' => 'numeric|nullable',
'manager_id' => 'numeric|nullable', 'manager_id' => 'numeric|nullable',
]; ];
/** /**

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/js/dist/all.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -1,8 +1,8 @@
{ {
"/js/build/vue.js": "/js/build/vue.js?id=8a5d892d5cd2d6195c15", "/js/build/vue.js": "/js/build/vue.js?id=385b35bd13d097237275",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=5002486f605469c322ed", "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=5002486f605469c322ed",
"/css/build/app.css": "/css/build/app.css?id=0c4d55f9abe9a4efe526", "/css/build/app.css": "/css/build/app.css?id=0c4d55f9abe9a4efe526",
"/js/build/vue.js.map": "/js/build/vue.js.map?id=770b634768b61b2b8104", "/js/build/vue.js.map": "/js/build/vue.js.map?id=d694e63456d4ed2ba993",
"/css/build/AdminLTE.css.map": "/css/build/AdminLTE.css.map?id=fa40c591a5b361cb0761", "/css/build/AdminLTE.css.map": "/css/build/AdminLTE.css.map?id=fa40c591a5b361cb0761",
"/css/build/app.css.map": "/css/build/app.css.map?id=5a1bc8c3be0d3da37d0a", "/css/build/app.css.map": "/css/build/app.css.map?id=5a1bc8c3be0d3da37d0a",
"/css/all.css": "/css/all.css?id=2d7f3b6654a518d7e0c5", "/css/all.css": "/css/all.css?id=2d7f3b6654a518d7e0c5",
@ -13,9 +13,9 @@
"/css/signature-pad.min.css": "/css/signature-pad.min.css?id=6a89d3cd901305e66ced", "/css/signature-pad.min.css": "/css/signature-pad.min.css?id=6a89d3cd901305e66ced",
"/css/blue.png": "/css/blue.png?id=4c85d6a97173123bd14a", "/css/blue.png": "/css/blue.png?id=4c85d6a97173123bd14a",
"/css/blue@2x.png": "/css/blue@2x.png?id=62c67c6a822439e8a4ac", "/css/blue@2x.png": "/css/blue@2x.png?id=62c67c6a822439e8a4ac",
"/js/dist/all.js": "/js/dist/all.js?id=f68abd82226bf29870c0", "/js/dist/all.js": "/js/dist/all.js?id=8acd0023559eaaddd6c5",
"/js/build/all.js": "/js/build/all.js?id=f68abd82226bf29870c0", "/js/build/all.js": "/js/build/all.js?id=8acd0023559eaaddd6c5",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=6804bda467f56a251e32", "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=7223478ba9bf2c6dfb4f",
"/js/dist/bootstrap-table-simple-view.js": "/js/dist/bootstrap-table-simple-view.js?id=3926b8f4aaad6ca20d31", "/js/dist/bootstrap-table-simple-view.js": "/js/dist/bootstrap-table-simple-view.js?id=3926b8f4aaad6ca20d31",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=8340c60bfbc12c34d2e6" "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=8340c60bfbc12c34d2e6"
} }

View file

@ -1,74 +1,99 @@
<style> <style>
tr { tr {
padding-left:30px; padding-left:30px;
} }
</style> </style>
<template> <template>
<tr v-show="processDetail"> <tr v-show="processDetail">
<td colspan="3"> <td colspan="5">
<h4 class="modal-title">Import File:</h4> <div class="col-md-2 text-left">
<div class="dynamic-form-row">
<div class="col-md-4 col-xs-12">
<label for="import-type">Import Type:</label>
</div>
<div class="col-md-4 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">
<label for="import-update">Update Existing Values?:</label>
</div>
<div class="col-md-4 col-xs-12">
<input type="checkbox" name="import-update" v-model="options.update">
</div>
</div> </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">
<div class="col-md-12" style="padding-top: 30px;"> <h4 class="modal-title">Import File:</h4>
<table class="table"> <div class="dynamic-form-row">
<thead> <div class="col-md-5 col-xs-12">
<th>Header Field</th> <label for="import-type">Import Type:</label>
<th>Import Field</th> </div>
<th>Sample Value</th> <div class="col-md-7 col-xs-12">
</thead> <select2 :options="options.importTypes" v-model="options.importType" required>
<tbody> <option disabled value="0"></option>
<template v-for="(header, index) in file.header_row">
<tr>
<td>
<label :for="header" class="controllabel">{{ header }}</label>
</td>
<td>
<div required>
<select2 :options="columns" v-model="columnMappings[header]">
<option value="0">Do Not Import</option>
</select2> </select2>
</div> </div>
</td> </div>
<td> <div class="dynamic-form-row">
<div>{{ activeFile.first_row[index] }}</div> <div class="col-md-5 col-xs-12">
</td> <label for="import-update">Update Existing Values?:</label>
</tr> </div>
</template> <div class="col-md-7 col-xs-12">
</tbody> <input type="checkbox" name="import-update" v-model="options.update">
</table> </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="text-left" style="padding-top: 30px;">
<table class="table table-striped snipe-table">
<thead>
<th>Header Field</th>
<th>Import Field</th>
<th>Sample Value</th>
</thead>
<tbody>
<template v-for="(header, index) in file.header_row">
<tr>
<td>
<label :for="header" class="controllabel">{{ header }}</label>
</td>
<td>
<div required>
<select2 :options="columns" v-model="columnMappings[header]">
<option value="0">Do Not Import</option>
</select2>
</div>
</td>
<td>
<div>{{ activeFile.first_row[index] }}</div>
</td>
</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> </div>
</td> </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> </tr>
</template> </template>
@ -143,7 +168,10 @@ tr {
{id: 'jobtitle', text: 'Job Title' }, {id: 'jobtitle', text: 'Job Title' },
{id: 'last_name', text: 'Last Name' }, {id: 'last_name', text: 'Last Name' },
{id: 'phone_number', text: 'Phone Number' }, {id: 'phone_number', text: 'Phone Number' },
{id: 'department', text: 'Department'} {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, customFields: this.customFields,
@ -169,14 +197,14 @@ tr {
switch(this.options.importType) { switch(this.options.importType) {
case 'asset': case 'asset':
return this.columnOptions.general return this.columnOptions.general
.concat(this.columnOptions.assets) .concat(this.columnOptions.assets)
.concat(this.columnOptions.customFields) .concat(this.columnOptions.customFields)
.sort(sorter); .sort(sorter);
case 'consumable': case 'consumable':
return this.columnOptions.general return this.columnOptions.general
.concat(this.columnOptions.consumables) .concat(this.columnOptions.consumables)
.sort(sorter); .sort(sorter);
case 'license': case 'license':
return this.columnOptions.general.concat(this.columnOptions.licenses).sort(sorter); return this.columnOptions.general.concat(this.columnOptions.licenses).sort(sorter);
case 'user': case 'user':
@ -212,6 +240,7 @@ tr {
this.statusText = "Processing..."; this.statusText = "Processing...";
this.$http.post(route('api.imports.importFile', this.file.id), { this.$http.post(route('api.imports.importFile', this.file.id), {
'import-update': this.options.update, 'import-update': this.options.update,
'send-welcome': this.options.send_welcome,
'import-type': this.options.importType, 'import-type': this.options.importType,
'column-mappings': this.columnMappings 'column-mappings': this.columnMappings
}).then( ({body}) => { }).then( ({body}) => {