Importer rework (#3100)

* Step 1 of refactoring importer to use separate classes.

* Port web importer.  Fix an issue with validation where index 0 would be treated as false and cause weird results.

* Farewall, AssetImport.  You've served us well.
This commit is contained in:
Daniel Meltzer 2016-12-26 18:16:42 -05:00 committed by snipe
parent 8857faee65
commit 57374955a8
9 changed files with 1230 additions and 1453 deletions

View file

@ -1,463 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Models\Location;
use App\Models\Category;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\Asset;
use App\Models\Manufacturer;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Illuminate\Console\Command;
use League\Csv\Reader;
class AssetImportCommand extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'snipeit:asset-import';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import Assets from CSV';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
$filename = $this->argument('filename');
if (!$this->option('testrun')=='true') {
$this->comment('======= Importing Assets from '.$filename.' =========');
} else {
$this->comment('====== TEST ONLY Asset Import for '.$filename.' ====');
$this->comment('============== NO DATA WILL BE WRITTEN ==============');
}
if (! ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
$csv = Reader::createFromPath($this->argument('filename'));
$csv->setNewline("\r\n");
$csv->setOffset(1);
$duplicates = '';
// Loop through the records
$nbInsert = $csv->each(function ($row) use ($duplicates) {
$status_id = 1;
// Let's just map some of these entries to more user friendly words
// User's name
if (array_key_exists('0', $row)) {
$user_name = trim($row[0]);
} else {
$user_name = '';
}
// User's email
if (array_key_exists('1', $row)) {
$user_email = trim($row[1]);
} else {
$user_email = '';
}
// User's email
if (array_key_exists('2', $row)) {
$user_username = trim($row[2]);
} else {
$user_username = '';
}
// Asset Name
if (array_key_exists('3', $row)) {
$user_asset_asset_name = trim($row[3]);
} else {
$user_asset_asset_name = '';
}
// Asset Category
if (array_key_exists('4', $row)) {
$user_asset_category = trim($row[4]);
} else {
$user_asset_category = '';
}
// Asset Name
if (array_key_exists('5', $row)) {
$user_asset_name = trim($row[5]);
} else {
$user_asset_name = '';
}
// Asset Manufacturer
if (array_key_exists('6', $row)) {
$user_asset_mfgr = trim($row[6]);
} else {
$user_asset_mfgr = '';
}
// Asset model number
if (array_key_exists('7', $row)) {
$user_asset_modelno = trim($row[7]);
} else {
$user_asset_modelno = '';
}
// Asset serial number
if (array_key_exists('8', $row)) {
$user_asset_serial = trim($row[8]);
} else {
$user_asset_serial = '';
}
// Asset tag
if (array_key_exists('9', $row)) {
$user_asset_tag = trim($row[9]);
} else {
$user_asset_tag = '';
}
// Asset location
if (array_key_exists('10', $row)) {
$user_asset_location = trim($row[10]);
} else {
$user_asset_location = '';
}
// Asset notes
if (array_key_exists('11', $row)) {
$user_asset_notes = trim($row[11]);
} else {
$user_asset_notes = '';
}
// Asset purchase date
if (array_key_exists('12', $row)) {
if ($row[12]!='') {
$user_asset_purchase_date = date("Y-m-d 00:00:01", strtotime($row[12]));
} else {
$user_asset_purchase_date = '';
}
} else {
$user_asset_purchase_date = '';
}
// Asset purchase cost
if (array_key_exists('13', $row)) {
if ($row[13]!='') {
$user_asset_purchase_cost = trim($row[13]);
} else {
$user_asset_purchase_cost = '';
}
} else {
$user_asset_purchase_cost = '';
}
// Asset Company Name
if (array_key_exists('14', $row)) {
if ($row[14]!='') {
$user_asset_company_name = trim($row[14]);
} else {
$user_asset_company_name= '';
}
} else {
$user_asset_company_name = '';
}
// A number was given instead of a name
if (is_numeric($user_name)) {
$this->comment('User '.$user_name.' is not a name - assume this user already exists');
$user_username = '';
$first_name = '';
$last_name = '';
// No name was given
} elseif ($user_name=='') {
$this->comment('No user data provided - skipping user creation, just adding asset');
$first_name = '';
$last_name = '';
//$user_username = '';
} else {
$user_email_array = User::generateFormattedNameFromFullName($this->option('email_format'), $user_name);
$first_name = $user_email_array['first_name'];
$last_name = $user_email_array['last_name'];
if ($user_email=='') {
$user_email = $user_email_array['username'].'@'.config('app.domain');
}
if ($user_username=='') {
if ($this->option('username_format')=='email') {
$user_username = $user_email;
} else {
$user_name_array = User::generateFormattedNameFromFullName($this->option('username_format'), $user_name);
$user_username = $user_name_array['username'];
}
}
}
$this->comment('Full Name: '.$user_name);
$this->comment('First Name: '.$first_name);
$this->comment('Last Name: '.$last_name);
$this->comment('Username: '.$user_username);
$this->comment('Email: '.$user_email);
$this->comment('Category Name: '.$user_asset_category);
$this->comment('Item: '.$user_asset_name);
$this->comment('Manufacturer ID: '.$user_asset_mfgr);
$this->comment('Model No: '.$user_asset_modelno);
$this->comment('Serial No: '.$user_asset_serial);
$this->comment('Asset Tag: '.$user_asset_tag);
$this->comment('Location: '.$user_asset_location);
$this->comment('Purchase Date: '.$user_asset_purchase_date);
$this->comment('Purchase Cost: '.$user_asset_purchase_cost);
$this->comment('Notes: '.$user_asset_notes);
$this->comment('Company Name: '.$user_asset_company_name);
$this->comment('------------- Action Summary ----------------');
if ($user_username!='') {
if ($user = User::MatchEmailOrUsername($user_username, $user_email)
->whereNotNull('username')->first()) {
$this->comment('User '.$user_username.' already exists');
} else {
$user = new \App\Models\User;
$password = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
$user->first_name = $first_name;
$user->last_name = $last_name;
$user->username = $user_username;
$user->email = $user_email;
$user->permissions = '{user":1}';
$user->password = bcrypt($password);
$user->activated = 1;
if ($user->save()) {
$this->comment('User '.$first_name.' created');
} else {
$this->error('ERROR CREATING User '.$first_name.' '.$last_name);
$this->error($user->getErrors());
}
}
} else {
$user = new User;
}
// Check for the location match and create it if it doesn't exist
if ($location = Location::where('name', e($user_asset_location))->first()) {
$this->comment('Location '.$user_asset_location.' already exists');
} else {
$location = new Location();
if ($user_asset_location!='') {
$location->name = e($user_asset_location);
$location->address = '';
$location->city = '';
$location->state = '';
$location->country = '';
$location->user_id = 1;
if (!$this->option('testrun')=='true') {
if ($location->save()) {
$this->comment('Location '.$user_asset_location.' was created');
} else {
$this->error('Something went wrong! Location '.$user_asset_location.' was NOT created');
$this->error($location->getErrors());
}
} else {
$this->comment('Location '.$user_asset_location.' was (not) created - test run only');
}
} else {
$this->comment('No location given, so none created.');
}
}
if (e($user_asset_category)=='') {
$category_name = 'Unnamed Category';
} else {
$category_name = e($user_asset_category);
}
// Check for the category match and create it if it doesn't exist
if ($category = Category::where('name', e($category_name))->where('category_type', 'asset')->first()) {
$this->comment('Category '.$category_name.' already exists');
} else {
$category = new Category();
$category->name = e($category_name);
$category->category_type = 'asset';
$category->user_id = 1;
if ($category->save()) {
$this->comment('Category '.$user_asset_category.' was created');
} else {
$this->error('Something went wrong! Category '.$user_asset_category.' was NOT created');
$this->error($category->getErrors());
}
}
// Check for the manufacturer match and create it if it doesn't exist
if ($manufacturer = Manufacturer::where('name', e($user_asset_mfgr))->first()) {
$this->comment('Manufacturer '.$user_asset_mfgr.' already exists');
} else {
$manufacturer = new Manufacturer();
$manufacturer->name = e($user_asset_mfgr);
$manufacturer->user_id = 1;
if ($manufacturer->save()) {
$this->comment('Manufacturer '.$user_asset_mfgr.' was created');
} else {
$this->error('Something went wrong! Manufacturer '.$user_asset_mfgr.' was NOT created: '. $manufacturer->getErrors()->first());
}
}
// Check for the asset model match and create it if it doesn't exist
if ($asset_model = AssetModel::where('name', e($user_asset_name))->where('modelno', e($user_asset_modelno))->where('category_id', $category->id)->where('manufacturer_id', $manufacturer->id)->first()) {
$this->comment('The Asset Model '.$user_asset_name.' with model number '.$user_asset_modelno.' already exists');
} else {
$asset_model = new AssetModel();
$asset_model->name = e($user_asset_name);
$asset_model->manufacturer_id = $manufacturer->id;
$asset_model->modelno = e($user_asset_modelno);
$asset_model->category_id = $category->id;
$asset_model->user_id = 1;
if ($asset_model->save()) {
$this->comment('Asset Model '.$user_asset_name.' with model number '.$user_asset_modelno.' was created');
} else {
$this->error('Something went wrong! Asset Model '.$user_asset_name.' was NOT created: '.$asset_model->getErrors()->first());
}
}
// Check for the asset company match and create it if it doesn't exist
if ($user_asset_company_name!='') {
if ($company = Company::where('name', e($user_asset_company_name))->first()) {
$this->comment('Company '.$user_asset_company_name.' already exists');
} else {
$company = new Company();
$company->name = e($user_asset_company_name);
if ($company->save()) {
$this->comment('Company '.$user_asset_company_name.' was created');
} else {
$this->error('Something went wrong! Company '.$user_asset_company_name.' was NOT created: '.$company->getErrors()->first());
}
}
} else {
$company = new Company();
}
// Check for the asset match and create it if it doesn't exist
if ($asset = Asset::where('asset_tag', e($user_asset_tag))->first()) {
$this->comment('The Asset with asset tag '.$user_asset_tag.' already exists');
} else {
$asset = new Asset();
$asset->name = e($user_asset_asset_name);
if ($user_asset_purchase_date!='') {
$asset->purchase_date = $user_asset_purchase_date;
} else {
$asset->purchase_date = null;
}
if ($user_asset_purchase_cost!='') {
$asset->purchase_cost = ParseFloat(e($user_asset_purchase_cost));
} else {
$asset->purchase_cost = 0.00;
}
$asset->serial = e($user_asset_serial);
$asset->asset_tag = e($user_asset_tag);
$asset->model_id = $asset_model->id;
$asset->assigned_to = $user->id;
$asset->rtd_location_id = $location->id;
$asset->user_id = 1;
$asset->status_id = $status_id;
$asset->company_id = $company->id;
if ($user_asset_purchase_date!='') {
$asset->purchase_date = $user_asset_purchase_date;
} else {
$asset->purchase_date = null;
}
$asset->notes = e($user_asset_notes);
if ($asset->save()) {
$this->comment('Asset '.$user_asset_name.' with serial number '.$user_asset_serial.' was created');
} else {
$this->error('Something went wrong! Asset '.$user_asset_name.' was NOT created: '.$asset->getErrors()->first());
}
}
$this->comment('=====================================');
return true;
});
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return array(
array('filename', InputArgument::REQUIRED, 'File for the CSV import.'),
);
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('email_format', null, InputOption::VALUE_REQUIRED, 'The format of the email addresses that should be generated. Options are firstname.lastname, firstname, filastname', null),
array('username_format', null, InputOption::VALUE_REQUIRED, 'The format of the username that should be generated. Options are firstname.lastname, firstname, filastname, email', null),
array('testrun', null, InputOption::VALUE_REQUIRED, 'Test the output without writing to the database or not.', null),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ use App\Http\Requests\AssetCheckinRequest;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\AssetRequest;
use App\Http\Requests\ItemImportRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetMaintenance;
@ -853,46 +854,29 @@ class AssetsController extends Controller
* @since [v2.0]
* @return Redirect
*/
public function postProcessImportFile()
public function postProcessImportFile(ItemImportRequest $request)
{
// php artisan asset-import:csv path/to/your/file.csv --domain=yourdomain.com --email_format=firstname.lastname
$filename = config('app.private_uploads') . '/imports/assets/' . request('filename');
$this->authorize('create', Asset::class);
$importOptions = ['filename'=> $filename,
'--email_format'=>'firstname.lastname',
'--username_format'=>'firstname.lastname',
'--web-importer' => true,
'--user_id' => Auth::id(),
'--item-type' => request('import-type'),
];
if (request('import-update')) {
$importOptions['--update'] = true;
}
$errors = $request->import();
$return = Artisan::call('snipeit:import', $importOptions);
$display_output = Artisan::output();
$file = config('app.private_uploads').'/imports/assets/'.str_replace('.csv', '', $filename).'-output-'.date("Y-m-d-his").'.txt';
file_put_contents($file, $display_output);
// We use hardware instead of asset in the url
$redirectTo = "hardware";
switch(request('import-type')) {
case "asset":
$redirectTo = "hardware";
$redirectTo = "hardware.index";
break;
case "accessory":
$redirectTo = "accessories";
$redirectTo = "accessories.index";
break;
case "consumable":
$redirectTo = "consumables";
$redirectTo = "consumables.index";
break;
}
if ($return === 0) { //Success
return redirect()->to(route($redirectTo))->with('success', trans('admin/hardware/message.import.success'));
} elseif ($return === 1) { // Failure
return redirect()->back()->with('import_errors', json_decode($display_output))->with('error', trans('admin/hardware/message.import.error'));
if ($errors) { //Failure
return redirect()->back()->with('import_errors', json_decode(json_encode($errors)))->with('error', trans('admin/hardware/message.import.error'));
}
dd("Shouldn't be here");
return redirect()->to(route($redirectTo))->with('success', trans('admin/hardware/message.import.success'));
}

View file

@ -0,0 +1,83 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class ItemImportRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
public function import()
{
$filename = config('app.private_uploads') . '/imports/assets/' . $this->get('filename');
$importerClass = Importer::class;
switch ($this->get('import-type')) {
case "asset":
$importerClass = 'App\Importer\AssetImporter';
break;
case "accessory":
$importerClass = 'App\Importer\AccessoryImporter';
break;
case "component":
die("This is not implemented yet");
$importerClass = ComponentImporter::class;
break;
case "consumable":
$importerClass = 'App\Importer\ConsumableImporter';
break;
}
$importer = new $importerClass(
$filename,
[$this, 'log'],
[$this, 'progress'],
[$this, 'errorCallback'],
false, /*testrun*/
Auth::id(),
$this->has('import-update'),
'firstname.lastname'
);
$importer->import();
return $this->errors;
}
public function log($string)
{
return; // FUTURE IMPLEMENTATION
}
public function progress($count)
{
// Open for future
return;
}
public function errorCallback($item, $field, $errorString)
{
$this->errors[$item->name][$field] = $errorString;
}
private $errors;
}

View file

@ -0,0 +1,110 @@
<?php
/**
* Created by PhpStorm.
* User: parallelgrapefruit
* Date: 12/24/16
* Time: 12:56 PM
*/
namespace App\Importer;
use App\Helpers\Helper;
use App\Models\Accessory;
class AccessoryImporter extends ItemImporter
{
protected $accessories;
function __construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun = false, $user_id = -1, $updating = false, $usernameFormat = null)
{
parent::__construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun, $user_id, $updating, $usernameFormat);
$this->accessories = Accessory::all();
}
protected function handle($row)
{
parent::handle($row); // TODO: Change the autogenerated stub
$this->createAccessoryIfNotExists();
}
/**
* Create an accessory if a duplicate does not exist
*
* @author Daniel Melzter
* @since 3.0
*/
public function createAccessoryIfNotExists()
{
$accessory = null;
$editingAccessory = false;
$this->log("Creating Accessory");
$accessory = $this->accessories->search(function ($key) {
return strcasecmp($key->name, $this->item['item_name']) == 0;
});
if($accessory) {
$editingAccessory = true;
if (!$this->updating) {
$this->log('A matching Accessory ' . $this->item["item_name"] . ' already exists. ');
return;
}
} else {
$this->log("No Matching Accessory, Creating a new one");
$accessory = new Accessory();
}
if (!$editingAccessory) {
$accessory->name = $this->item["item_name"];
}
if (!empty($this->item["purchase_date"])) {
$accessory->purchase_date = $this->item["purchase_date"];
} else {
$accessory->purchase_date = null;
}
if (!empty($this->item["purchase_cost"])) {
$accessory->purchase_cost = Helper::ParseFloat($this->item["purchase_cost"]);
}
if (isset($this->item["location"])) {
$accessory->location_id = $this->item["location"]->id;
}
$accessory->user_id = $this->user_id;
if (isset($this->item["company"])) {
$accessory->company_id = $this->item["company"]->id;
}
if (!empty($this->item["order_number"])) {
$accessory->order_number = $this->item["order_number"];
}
if (isset($this->item["category"])) {
$accessory->category_id = $this->item["category"]->id;
}
//TODO: Implement
// $accessory->notes = e($item_notes);
if (!empty($this->item["requestable"])) {
$accessory->requestable = filter_var($this->item["requestable"], FILTER_VALIDATE_BOOLEAN);
}
//Must have at least zero of the item if we import it.
if (!empty($this->item["quantity"])) {
if ($this->item["quantity"] > -1) {
$accessory->qty = $this->item["quantity"];
} else {
$accessory->qty = 1;
}
}
if (!$this->testRun) {
if ($accessory->save()) {
$accessory->logCreate('Imported using CSV Importer');
$this->log('Accessory ' . $this->item["item_name"] . ' was created');
// $this->comment('Accessory ' . $this->item["item_name"] . ' was created');
} else {
$this->jsonError($accessory,'Accessory', $accessory->getErrors()) ;
}
} else {
$this->log('TEST RUN - Accessory ' . $this->item["item_name"] . ' not created');
}
}
}

View file

@ -0,0 +1,176 @@
<?php
/**
* Created by PhpStorm.
* User: parallelgrapefruit
* Date: 12/24/16
* Time: 12:45 PM
*/
namespace App\Importer;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Category;
use App\Models\Manufacturer;
class AssetImporter extends ItemImporter
{
protected $assets;
function __construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun = false, $user_id = -1, $updating = false, $usernameFormat = null)
{
parent::__construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun, $user_id, $updating, $usernameFormat);
$this->assets = Asset::all();
}
protected function handle($row)
{
// ItemImporter handles the general fetching.
parent::handle($row);
foreach ($this->customFields as $customField) {
if ($this->item['custom_fields'][$customField->db_column_name()] = $this->array_smart_custom_field_fetch($row, $customField)) {
$this->log('Custom Field '. $customField->name.': '.$this->array_smart_custom_field_fetch($row, $customField));
}
}
$this->createAssetIfNotExists($row);
}
/**
* Create the asset if it does not exist.
*
* @author Daniel Melzter
* @since 3.0
* @param array $row
* @return Asset|mixed|null
*/
public function createAssetIfNotExists(array $row)
{
$asset = null;
$editingAsset = false;
$asset = $this->assets->search(function ($key) {
return strcasecmp($key->asset_tag, $this->item['asset_tag']) == 0;
});
if($asset) {
$editingAsset = true;
if (!$this->updating) {
$this->log('A matching Asset ' . $this->item['asset_tag'] . ' already exists');
return $asset;
}
}
if (!$asset) {
$this->log("No Matching Asset, Creating a new one");
$asset = new Asset;
}
$asset_serial = $this->array_smart_fetch($row, "serial number");
$asset_image = $this->array_smart_fetch($row, "image");
$asset_warranty_months = intval($this->array_smart_fetch($row, "warranty months"));
if (empty($asset_warranty_months)) {
$asset_warranty_months = null;
}
// Check for the asset model match and create it if it doesn't exist
if (!($editingAsset && empty($this->array_smart_fetch($row, 'model name')))) {
// Ignore the asset_model
isset($this->item["category"]) || $this->item["category"] = new Category();
isset($this->item["manufacturer"]) || $this->item["manufacturer"] = new Manufacturer();
$asset_model = $this->createOrFetchAssetModel($row, $this->item["category"], $this->item["manufacturer"]);
}
$item_supplier = $this->array_smart_fetch($row, "supplier");
// If we're editing, only update if value isn't empty
if (!($editingAsset && empty($item_supplier))) {
$supplier = $this->createOrFetchSupplier($item_supplier);
}
$this->log('Serial No: '.$asset_serial);
$this->log('Asset Tag: '.$this->item['asset_tag']);
$this->log('Notes: '.$this->item["notes"]);
$this->log('Warranty Months: ' . $asset_warranty_months);
if (isset($this->item["status_label"])) {
$status_id = $this->item["status_label"]->id;
} else if (!$editingAsset) {
// Assume if we are editing, we already have a status and can ignore.
$this->log("No status field found, defaulting to first status.");
$status_id = $this->status_labels->first()->id;
}
if (!$editingAsset) {
$asset->asset_tag = $this->item['asset_tag']; // This doesn't need to be guarded for empty because it's the key we use to identify the asset.
}
if (!empty($this->item['item_name'])) {
$asset->name = $this->item["item_name"];
}
if (!empty($this->item["purchase_date"])) {
$asset->purchase_date = $this->item["purchase_date"];
}
if (array_key_exists('custom_fields', $this->item)) {
foreach ($this->item['custom_fields'] as $custom_field => $val) {
$asset->{$custom_field} = $val;
}
}
if (!empty($this->item["purchase_cost"])) {
//TODO How to generalize this for not USD?
$purchase_cost = substr($this->item["purchase_cost"], 0, 1) === '$' ? substr($this->item["purchase_cost"], 1) : $this->item["purchase_cost"];
// $asset->purchase_cost = number_format($purchase_cost, 2, '.', '');
$asset->purchase_cost = Helper::ParseFloat($purchase_cost);
$this->log("Asset cost parsed: " . $asset->purchase_cost);
} else {
$asset->purchase_cost = 0.00;
}
if (!empty($asset_serial)) {
$asset->serial = $asset_serial;
}
if (!empty($asset_warranty_months)) {
$asset->warranty_months = $asset_warranty_months;
}
if (isset($asset_model)) {
$asset->model_id = $asset_model->id;
}
if ($this->item["user"]) {
$asset->assigned_to = $this->item["user"]->id;
}
if (isset($this->item["location"])) {
$asset->rtd_location_id = $this->item["location"]->id;
}
$asset->user_id = $this->user_id;
if (isset($status_id)) {
$asset->status_id = $status_id;
}
if (isset($this->item["company"])) {
$asset->company_id = $this->item["company"]->id;
}
if ($this->item["order_number"]) {
$asset->order_number = $this->item["order_number"];
}
if (isset($supplier)) {
$asset->supplier_id = $supplier->id;
}
if ($this->item["notes"]) {
$asset->notes = $this->item["notes"];
}
if (!empty($asset_image)) {
$asset->image = $asset_image;
}
if (!$editingAsset) {
$this->assets->add($asset);
}
if (!$this->testRun) {
if ($asset->save()) {
$asset->logCreate('Imported using csv importer');
$this->log('Asset ' . $this->item["item_name"] . ' with serial number ' . $asset_serial . ' was created');
} else {
$this->jsonError($asset, 'Asset', $asset->getErrors());
}
}
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* Created by PhpStorm.
* User: parallelgrapefruit
* Date: 12/24/16
* Time: 1:03 PM
*/
namespace App\Importer;
use App\Helpers\Helper;
use App\Models\Consumable;
class ConsumableImporter extends ItemImporter
{
protected $consumables;
function __construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun = false, $user_id = -1, $updating = false, $usernameFormat = null)
{
parent::__construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun, $user_id, $updating, $usernameFormat);
$this->consumables = Consumable::all();
}
protected function handle($row)
{
parent::handle($row); // TODO: Change the autogenerated stub
$this->createConsumableIfNotExists();
}
/**
* Create a consumable if a duplicate does not exist
*
* @author Daniel Melzter
* @since 3.0
*/
public function createConsumableIfNotExists()
{
$consumable = null;
$editingConsumable = false;
$this->log("Creating Consumable");
$consumable = $this->consumables->search(function ($key) {
return strcasecmp($key->name, $this->item['item_name']) == 0;
});
if($consumable) {
$editingConsumable = true;
if (!$this->updating) {
$this->log('A matching Consumable ' . $this->item["item_name"] . ' already exists. ');
return;
}
} else {
$this->log("No matching consumable, creating one");
$consumable = new Consumable();
}
if (!$editingConsumable) {
$consumable->name = $this->item["item_name"];
}
if (!empty($this->item["purchase_date"])) {
$consumable->purchase_date = $this->item["purchase_date"];
} else {
$consumable->purchase_date = null;
}
if (!empty($this->item["purchase_cost"])) {
$consumable->purchase_cost = Helper::ParseFloat($this->item["purchase_cost"]);
}
if (isset($this->item["location"])) {
$consumable->location_id = $this->item["location"]->id;
}
$consumable->user_id = $this->user_id;
if (isset($this->item["company"])) {
$consumable->company_id = $this->item["company"]->id;
}
if (!empty($this->item["order_number"])) {
$consumable->order_number = $this->item["order_number"];
}
if (isset($this->item["category"])) {
$consumable->category_id = $this->item["category"]->id;
}
// TODO:Implement
//$consumable->notes= e($this->item_notes);
if (!empty($this->item["requestable"])) {
$consumable->requestable = filter_var($this->item["requestable"], FILTER_VALIDATE_BOOLEAN);
}
if (!empty($this->item["quantity"])) {
if ($this->item["quantity"] > -1) {
$consumable->qty = $this->item["quantity"];
} else {
$consumable->qty = 1;
}
}
if (!$this->testRun) {
if ($consumable->save()) {
$consumable->logCreate('Imported using CSV Importer');
$this->log("Consumable " . $this->item["item_name"] . ' was created');
} else {
$this->jsonError($consumable, 'Consumable', $consumable->getErrors());
}
} else {
$this->log('TEST RUN - Consumable ' . $this->item['item_name'] . ' not created');
}
}
}

285
app/Importer/Importer.php Normal file
View file

@ -0,0 +1,285 @@
<?php
namespace App\Importer;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use ForceUTF8\Encoding;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use League\Csv\Reader;
abstract class Importer
{
/**
* @var string
*/
private $filename;
private $csv;
/**
* Should we persist to database?
* @var bool
*/
protected $testRun;
/**
* Id of User performing import
* @var
*/
protected $user_id;
/**
* Are we updating items in the import
* @var bool
*/
protected $updating;
/**
* @var callable
*/
protected $logCallback;
protected $tempPassword;
/**
* @var callable
*/
protected $progressCallback;
/**
* @var null
*/
protected $usernameFormat;
/**
* @var callable
*/
protected $errorCallback;
/**
* ObjectImporter constructor.
* @param string $filename
* @param callable $logCallback
* @param callable $progressCallback
* @param callable $errorCallback
* @param bool $testRun
* @param int $user_id
* @param bool $updating
* @param null $usernameFormat
*/
function __construct(string $filename,
$logCallback,
$progressCallback,
$errorCallback,
$testRun = false,
$user_id = -1,
$updating = false,
$usernameFormat = null)
{
$this->filename = $filename;
$this->csv = Reader::createFromPath($filename);
$this->csv->setNewLine('\r\n');
if (! ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
$this->testRun = $testRun;
if($user_id == -1) {
$user_id = Auth::id();
}
$this->user_id = $user_id;
$this->updating = $updating;
$this->logCallback = $logCallback;
$this->tempPassword = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
$this->progressCallback = $progressCallback;
$this->usernameFormat = $usernameFormat;
$this->errorCallback = $errorCallback;
}
// Cached Values for import lookups
protected $locations;
protected $categories;
protected $manufacturers;
protected $asset_models;
protected $companies;
protected $status_labels;
protected $suppliers;
protected $assets;
protected $accessories;
protected $consumables;
protected $customFields;
public function import()
{
// dd($this->csv->fetchAssoc());
$results = $this->normalizeInputArray($this->csv->fetchAssoc());
$this->initializeLookupArrays();
DB::transaction(function () use (&$results) {
Model::unguard();
$resultsCount = sizeof($results);
foreach ($results as $row) {
// Let's just map some of these entries to more user friendly words
// Fetch general items here, fetch item type specific items in respective methods
/** @var Asset, License, Accessory, or Consumable $item_type */
$this->handle($row);
call_user_func($this->progressCallback, $resultsCount);
$this->log('------------- Action Summary ----------------');
}
});
}
abstract protected function handle($row);
/**
* @param $results
* @return array
*/
public function normalizeInputArray($results): array
{
$newArray = [];
foreach ($results as $index => $arrayToNormalize) {
$newArray[$index] = array_change_key_case($arrayToNormalize);
}
return $newArray;
}
/**
* Load Cached versions of all used methods.
*/
public function initializeLookupArrays()
{
$this->locations = Location::All(['name', 'id']);
$this->categories = Category::All(['name', 'category_type', 'id']);
$this->manufacturers = Manufacturer::All(['name', 'id']);
$this->asset_models = AssetModel::All(['name', 'model_number', 'category_id', 'manufacturer_id', 'id']);
$this->companies = Company::All(['name', 'id']);
$this->status_labels = Statuslabel::All(['name', 'id']);
$this->suppliers = Supplier::All(['name', 'id']);
$this->customFields = CustomField::All(['name']);
}
/**
* Check to see if the given key exists in the array, and trim excess white space before returning it
*
* @author Daniel Melzter
* @since 3.0
* @param $array array
* @param $key string
* @param $default string
* @return string
*/
public function array_smart_fetch(array $array, $key, $default = '')
{
return array_key_exists(trim($key), $array) ? e(Encoding::fixUTF8(trim($array[ $key ]))) : $default;
}
/**
* Figure out the fieldname of the custom field
*
* @author A. Gianotto <snipe@snipe.net>
* @since 3.0
* @param $array array
* @return string
*/
public function array_smart_custom_field_fetch(array $array, $key)
{
$index_name = strtolower($key->name);
return array_key_exists($index_name, $array) ? e(trim($array[$index_name])) : '';
}
protected function log($string) {
call_user_func($this->logCallback, $string);
}
protected function jsonError($item, $field, $errorString) {
call_user_func($this->errorCallback, $item, $field, $errorString);
}
/**
* Finds the user matching given data, or creates a new one if there is no match
*
* @author Daniel Melzter
* @since 3.0
* @param $row array
* @return User Model w/ matching name
* @internal param string $user_username Username extracted from CSV
* @internal param string $user_email Email extracted from CSV
* @internal param string $first_name
* @internal param string $last_name
*/
protected function createOrFetchUser($row)
{
$user_name = $this->array_smart_fetch($row, "name");
$user_email = $this->array_smart_fetch($row, "email");
$user_username = $this->array_smart_fetch($row, "username");
$first_name = '';
$last_name = '';
// A number was given instead of a name
if (is_numeric($user_name)) {
$this->log('User '.$user_name.' is not a name - assume this user already exists');
$user_username = '';
// No name was given
} elseif (empty($user_name)) {
$this->log('No user data provided - skipping user creation, just adding asset');
//$user_username = '';
} else {
$user_email_array = User::generateFormattedNameFromFullName(Setting::getSettings()->email_format, $user_name);
$first_name = $user_email_array['first_name'];
$last_name = $user_email_array['last_name'];
if ($user_email=='') {
if (Setting::getSettings()->email_domain) {
$user_email = str_slug($user_email_array['username']).'@'.Setting::getSettings()->email_domain;
}
}
if ($user_username=='') {
if ($this->usernameFormat =='email') {
$user_username = $user_email;
} else {
$user_name_array = User::generateFormattedNameFromFullName(Setting::getSettings()->username_format, $user_name);
$user_username = $user_name_array['username'];
}
}
}
$this->log("--- User Data ---");
$this->log('Full Name: ' . $user_name);
$this->log('First Name: ' . $first_name);
$this->log('Last Name: ' . $last_name);
$this->log('Username: ' . $user_username);
$this->log('Email: ' . $user_email);
$this->log('--- End User Data ---');
$user = new User;
if ($this->testRun) {
return $user;
}
if (!empty($user_username)) {
if ($user = User::MatchEmailOrUsername($user_username, $user_email)
->whereNotNull('username')->first()) {
$this->log('User '.$user_username.' already exists');
} elseif (( $first_name != '') && ($last_name != '') && ($user_username != '')) {
$user = new User;
$user->first_name = $first_name;
$user->last_name = $last_name;
$user->username = $user_username;
$user->email = $user_email;
$user->activated = 1;
$user->password = $this->tempPassword;
if ($user->save()) {
$this->log('User '.$first_name.' created');
} else {
$this->jsonError($user,'User "' . $first_name . '"', $user->getErrors());
}
}
}
return $user;
}
}

View file

@ -0,0 +1,399 @@
<?php
namespace App\Importer;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Statuslabel;
use App\Models\Supplier;
class ItemImporter extends Importer
{
protected $item;
function __construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun = false, $user_id = -1, $updating = false, $usernameFormat = null)
{
parent::__construct($filename, $logCallback, $progressCallback, $errorCallback, $testRun, $user_id, $updating, $usernameFormat);
}
protected function handle($row)
{
$item_category = $this->array_smart_fetch($row, "category");
$item_company_name = $this->array_smart_fetch($row, "company");
$item_location = $this->array_smart_fetch($row, "location");
$item_manufacturer = $this->array_smart_fetch($row, "manufacturer");
$item_status_name = $this->array_smart_fetch($row, "status");
$this->item["item_name"] = $this->array_smart_fetch($row, "item name");
if ($this->array_smart_fetch($row, "purchase date")!='') {
$this->item["purchase_date"] = date("Y-m-d 00:00:01", strtotime($this->array_smart_fetch($row, "purchase date")));
} else {
$this->item["purchase_date"] = null;
}
$this->item["purchase_cost"] = $this->array_smart_fetch($row, "purchase cost");
$this->item["order_number"] = $this->array_smart_fetch($row, "order number");
$this->item["notes"] = $this->array_smart_fetch($row, "notes");
$this->item["quantity"] = $this->array_smart_fetch($row, "quantity");
$this->item["requestable"] = $this->array_smart_fetch($row, "requestable");
$this->item["asset_tag"] = $this->array_smart_fetch($row, "asset tag");
$this->item["user"] = $this->createOrFetchUser($row);
if (!($this->updating && empty($item_location))) {
$this->item["location"] = $this->createOrFetchLocation($item_location);
}
if (!($this->updating && empty($item_category))) {
$this->item["category"] = $this->createOrFetchCategory($item_category);
}
if (!($this->updating && empty($item_manufacturer))) {
$this->item["manufacturer"] = $this->createOrFetchManufacturer($item_manufacturer);
}
if (!($this->updating && empty($item_company_name))) {
$this->item["company"] = $this->createOrFetchCompany($item_company_name);
}
if (!($this->updating && empty($item_status_name))) {
$this->item["status_label"] = $this->createOrFetchStatusLabel($item_status_name);
}
}
/**
* Select the asset model if it exists, otherwise create it.
*
* @author Daniel Melzter
* @since 3.0
* @param array
* @param $category Category
* @param $manufacturer Manufacturer
* @return AssetModel
* @internal param $asset_modelno string
*/
public function createOrFetchAssetModel(array $row, $category, $manufacturer)
{
$asset_model_name = $this->array_smart_fetch($row, "model name");
$asset_modelNumber = $this->array_smart_fetch($row, "model number");
if ((empty($asset_model_name)) && (!empty($asset_modelNumber))) {
$asset_model_name = $asset_modelNumber;
} elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) {
$asset_model_name ='Unknown';
}
$this->log('Model Name: ' . $asset_model_name);
$this->log('Model No: ' . $asset_modelNumber);
$asset_model = null;
$editingModel = $this->updating;
$asset_model = $this->asset_models->search(function ($key) use ($asset_model_name, $asset_modelNumber) {
return strcasecmp($key->name, $asset_model_name) ==0
&& $key->model_number == $asset_modelNumber;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if(($asset_model !== false) && !$editingModel) {
return $this->asset_models[$asset_model];
} else {
$this->log("No Matching Model, Creating a new one");
$asset_model = new AssetModel();
}
if (($editingModel && ($asset_model_name != "Unknown"))
|| (!$editingModel)) {
$asset_model->name = $asset_model_name;
}
isset($manufacturer) && $manufacturer->exists() && $asset_model->manufacturer_id = $manufacturer->id;
isset($asset_modelNumber) && $asset_model->model_number = $asset_modelNumber;
if (isset($category) && $category->exists()) {
$asset_model->category_id = $category->id;
}
$asset_model->user_id = $this->user_id;
if (!$editingModel) {
$this->asset_models->add($asset_model);
}
if (!$this->testRun) {
if ($asset_model->save()) {
$this->log('Asset Model ' . $asset_model_name . ' with model number ' . $asset_modelNumber . ' was created');
return $asset_model;
} else {
$this->jsonError($asset_model,'Asset Model "' . $asset_model_name . '"', $asset_model->getErrors());
$this->log('Asset Model "' . $asset_model_name . '"' . $asset_model->getErrors());
return $asset_model;
}
} else {
$this->asset_models->add($asset_model);
return $asset_model;
}
}
/**
* Finds a category with the same name and item type in the database, otherwise creates it
*
* @author Daniel Melzter
* @since 3.0
* @param $asset_category string
* @return Category
* @internal param string $item_type
*/
public function createOrFetchCategory($asset_category)
{
// Magic to transform "AssetImporter" to "asset" or similar.
$classname = get_class($this);
$item_type = strtolower(substr($classname, 0, strpos($classname, 'Importer')));
if (empty($asset_category)) {
$asset_category = 'Unnamed Category';
}
$category = $this->categories->search(function ($key) use ($asset_category, $item_type) {
return (strcasecmp($key->name, $asset_category) == 0)
&& $key->category_type === $item_type;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if ($category !== false) {
return $this->categories[$category];
}
$category = new Category();
$category->name = $asset_category;
$category->category_type = $item_type;
$category->user_id = $this->user_id;
if (!$this->testRun) {
if ($category->save()) {
$this->categories->add($category);
$this->log('Category ' . $asset_category . ' was created');
return $category;
} else {
$this->jsonError($category, 'Category "'. $asset_category. '"', $category->getErrors());
return $category;
}
} else {
$this->categories->add($category);
return $category;
}
}
/**
* Fetch an existing company, or create new if it doesn't exist
*
* @author Daniel Melzter
* @since 3.0
* @param $asset_company_name string
* @return Company
*/
public function createOrFetchCompany($asset_company_name)
{
$company = $this->companies->search(function ($key) use($asset_company_name) {
return strcasecmp($key->name, $asset_company_name) == 0;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if($company !== false) {
$this->log('A matching Company ' . $asset_company_name . ' already exists');
return $this->companies[$company];
}
$company = new Company();
$company->name = $asset_company_name;
if (!$this->testRun) {
if ($company->save()) {
$this->companies->add($company);
$this->log('Company ' . $asset_company_name . ' was created');
return $company;
} else {
$this->log('Company', $company->getErrors());
}
} else {
$this->companies->add($company);
return $company;
}
}
/**
* Fetch the existing status label or create new if it doesn't exist.
*
* @author Daniel Melzter
* @since 3.0
* @param string $asset_statuslabel_name
* @return Statuslabel
*/
public function createOrFetchStatusLabel($asset_statuslabel_name)
{
if (empty($asset_statuslabel_name)) {
return null;
}
$status = $this->status_labels->search(function ($key) use($asset_statuslabel_name) {
return strcasecmp($key->name, $asset_statuslabel_name) == 0;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if ($status !== false) {
$this->log('A matching Status ' . $asset_statuslabel_name . ' already exists');
return $this->status_labels[$status];
}
$this->log("Creating a new status");
$status = new Statuslabel();
$status->name = $asset_statuslabel_name;
$status->deployable = 1;
$status->pending = 0;
$status->archived = 0;
if (!$this->testRun) {
if ($status->save()) {
$this->status_labels->add($status);
$this->log('Status ' . $asset_statuslabel_name . ' was created');
return $status;
} else {
$this->jsonError($status,'Status "'. $asset_statuslabel_name . '"', $status->getErrors());
return $status;
}
} else {
$this->status_labels->add($status);
return $status;
}
}
/**
* Finds a manufacturer with matching name, otherwise create it.
*
* @author Daniel Melzter
* @since 3.0
* @param $item_manufacturer string
* @return Manufacturer
*/
public function createOrFetchManufacturer($item_manufacturer)
{
if (empty($item_manufacturer)) {
$item_manufacturer='Unknown';
}
$manufacturer = $this->manufacturers->search(function ($key) use($item_manufacturer) {
return strcasecmp($key->name, $item_manufacturer) == 0;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if ($manufacturer !== false) {
$this->log('Manufacturer ' . $item_manufacturer . ' already exists') ;
return $this->manufacturers[$manufacturer];
}
//Otherwise create a manufacturer.
$manufacturer = new Manufacturer();
$manufacturer->name = $item_manufacturer;
$manufacturer->user_id = $this->user_id;
if (!$this->testRun) {
if ($manufacturer->save()) {
$this->manufacturers->add($manufacturer);
$this->log('Manufacturer ' . $manufacturer->name . ' was created');
return $manufacturer;
} else {
$this->jsonError($manufacturer, 'Manufacturer "'. $manufacturer->name . '"', $manufacturer->getErrors());
return $manufacturer;
}
} else {
$this->manufacturers->add($manufacturer);
return $manufacturer;
}
}
/**
* Checks the DB to see if a location with the same name exists, otherwise create it
*
* @author Daniel Melzter
* @since 3.0
* @param $asset_location string
* @return Location|null
*/
public function createOrFetchLocation($asset_location)
{
if (empty($asset_location)) {
$this->log('No location given, so none created.');
return null;
}
$location = $this->locations->search(function ($key) use($asset_location) {
return strcasecmp($key->name, $asset_location) == 0;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if ($location !== false) {
$this->log('Location ' . $asset_location . ' already exists');
return $this->locations[$location];
}
// No matching locations in the collection, create a new one.
$location = new Location();
$location->name = $asset_location;
$location->address = '';
$location->city = '';
$location->state = '';
$location->country = '';
$location->user_id = $this->user_id;
if (!$this->testRun) {
if ($location->save()) {
$this->locations->add($location);
$this->log('Location ' . $asset_location . ' was created');
return $location;
} else {
$this->log('Location', $location->getErrors()) ;
return $location;
}
} else {
$this->locations->add($location);
return $location;
}
}
/**
* Fetch an existing supplier or create new if it doesn't exist
*
* @author Daniel Melzter
* @since 3.0
* @param $row array
* @return Supplier
*/
public function createOrFetchSupplier($item_supplier)
{
if (empty($item_supplier)) {
$item_supplier='Unknown';
}
$supplier = $this->suppliers->search(function ($key) use($item_supplier) {
return strcasecmp($key->name, $item_supplier) == 0;
});
// We need strict compare here because the index returned above can be 0.
// This casts to false and causes false positives
if ($supplier !== false) {
$this->log('Supplier ' . $item_supplier . ' already exists');
return $this->suppliers[$supplier];
}
$supplier = new Supplier();
$supplier->name = $item_supplier;
$supplier->user_id = $this->user_id;
if (!$this->testRun) {
if ($supplier->save()) {
$this->suppliers->add($supplier);
$this->log('Supplier ' . $item_supplier . ' was created');
return $supplier;
} else {
$this->log('Supplier', $supplier->getErrors());
return $supplier;
}
} else {
$this->suppliers->add($supplier);
return $supplier;
}
}
}