mirror of
https://github.com/snipe/snipe-it.git
synced 2025-01-26 13:10:55 -08:00
Importer mapping - v1 (#3677)
* Move importer to an inline-template, allows for translations and easier passing of data from laravel to vue. * Pull the modal out into a dedicated partial, move importer to views/importer. * Add document of CSV->importer mappings. Reorganize some code. Progress. * Add header_row and first_row to imports table, and process upon uploading a file * Use an expandable table row instead of a modal for import processing. This should allow for field mapping interaction easier. * Fix import processing after moving method. * Frontend importer mapping improvements. Invert display so we show found columns and allow users to select an importer field to map to. Also implement sample data based on first row of csv. * Update select2. Maintain selected items properly. * Backend support for importing. Only works on the web importer currently. Definitely needs testing and polish. * We no longer use vue-modal plugin. * Add a column to track field mappings to the imports table. * Cleanup/rename methods+refactor * Save field mappings and import type when attempting an import, and repopulate these values when returning to the page. * Update debugbar to fix a bug in the debugbar code. * Fix asset tag detection. Also rename findMatch to be a bit clearer as to what it does. Remove logging to file of imports for http imports because it eats an incredible amouint of memory. This commit also moves imports out of the hardware namespace and into their own webcontroller and route prefix, remove dead code from AssetController as a result. * Dynamically limit options for select2 based on import type selected, and group them by item type. * Add user importer. Still need to implement emailing of passwords to new users, and probably test a bit more. This also bumps the memory limit for web imports up as well, I need to profile memory usage here before too long. * Query the db to find user matches rather than search the array. Performance is much much better. * Speed/memory improvements in importers. Move to querying the db rather than maintaining an array for all importers. Also only store the id of items when we import, rather than the full model. It saves a decent amount of memory. * Remove grouping of items in select2 With the values being set dynamically, the grouping is redundant. It also caused a regression with automatically guessing/matching field names. This is starting to get close. * Remove debug line on every create. * Switch migration to be text field instead of json field for compatibility with older mysql/mariadb * Fix asset import regression matching email address. * Rearrange travis order in attempt to fix null settings. * Use auth::id instead of fetching it off the user. Fixes a null object reference during seeding.
This commit is contained in:
parent
d12159c042
commit
61c6160b98
|
@ -1,398 +0,0 @@
|
|||
<?php
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use League\Csv\Reader;
|
||||
use App\Models\User;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\Asset;
|
||||
|
||||
class LicenseImportCommand extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'snipeit:license-import';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import Licenses 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 Licenses from '.$filename.' =========');
|
||||
} else {
|
||||
$this->comment('====== TEST ONLY License 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
|
||||
|
||||
if (array_key_exists('0', $row)) {
|
||||
$user_name = trim($row[0]);
|
||||
} else {
|
||||
$user_name = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('1', $row)) {
|
||||
$user_email = trim($row[1]);
|
||||
} else {
|
||||
$user_email = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('2', $row)) {
|
||||
$user_username = trim($row[2]);
|
||||
} else {
|
||||
$user_username = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('3', $row)) {
|
||||
$user_license_name = trim($row[3]);
|
||||
} else {
|
||||
$user_license_name = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('4', $row)) {
|
||||
$user_license_serial = trim($row[4]);
|
||||
} else {
|
||||
$user_license_serial = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('5', $row)) {
|
||||
$user_licensed_to_name = trim($row[5]);
|
||||
} else {
|
||||
$user_licensed_to_name = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('6', $row)) {
|
||||
$user_licensed_to_email = trim($row[6]);
|
||||
} else {
|
||||
$user_licensed_to_email = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('7', $row)) {
|
||||
$user_license_seats = trim($row[7]);
|
||||
} else {
|
||||
$user_license_seats = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('8', $row)) {
|
||||
$user_license_reassignable = trim($row[8]);
|
||||
if ($user_license_reassignable!='') {
|
||||
if ((strtolower($user_license_reassignable)=='yes') || (strtolower($user_license_reassignable)=='true') || ($user_license_reassignable=='1')) {
|
||||
$user_license_reassignable = 1;
|
||||
}
|
||||
} else {
|
||||
$user_license_reassignable = 0;
|
||||
}
|
||||
} else {
|
||||
$user_license_reassignable = 0;
|
||||
}
|
||||
|
||||
if (array_key_exists('9', $row)) {
|
||||
$user_license_supplier = trim($row[9]);
|
||||
} else {
|
||||
$user_license_supplier = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('10', $row)) {
|
||||
$user_license_maintained = trim($row[10]);
|
||||
|
||||
if ($user_license_maintained!='') {
|
||||
if ((strtolower($user_license_maintained)=='yes') || (strtolower($user_license_maintained)=='true') || ($user_license_maintained=='1')) {
|
||||
$user_license_maintained = 1;
|
||||
}
|
||||
} else {
|
||||
$user_license_maintained = 0;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
$user_license_maintained = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('11', $row)) {
|
||||
$user_license_notes = trim($row[11]);
|
||||
} else {
|
||||
$user_license_notes = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('12', $row)) {
|
||||
if ($row[12]!='') {
|
||||
$user_license_purchase_date = date("Y-m-d 00:00:01", strtotime($row[12]));
|
||||
} else {
|
||||
$user_license_purchase_date = '';
|
||||
}
|
||||
} else {
|
||||
$user_license_purchase_date = 0;
|
||||
}
|
||||
|
||||
if (array_key_exists('13', $row)) {
|
||||
$user_licensed_to_asset = trim($row[13]);
|
||||
} else {
|
||||
$user_licensed_to_asset = '';
|
||||
}
|
||||
|
||||
// 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 = '';
|
||||
// No name was given
|
||||
|
||||
} elseif ($user_name=='') {
|
||||
$this->comment('No user data provided - skipping user creation, just adding license');
|
||||
$first_name = '';
|
||||
$last_name = '';
|
||||
$user_username = '';
|
||||
|
||||
} else {
|
||||
|
||||
$name = explode(" ", $user_name);
|
||||
$first_name = $name[0];
|
||||
$email_last_name = '';
|
||||
$email_prefix = $first_name;
|
||||
|
||||
if (!array_key_exists(1, $name)) {
|
||||
$last_name='';
|
||||
$email_last_name = $last_name;
|
||||
$email_prefix = $first_name;
|
||||
} else {
|
||||
$last_name = str_replace($first_name, '', $user_name);
|
||||
|
||||
if ($this->option('email_format')=='filastname') {
|
||||
$email_last_name.=str_replace(' ', '', $last_name);
|
||||
$email_prefix = $first_name[0].$email_last_name;
|
||||
|
||||
} elseif ($this->option('email_format')=='firstname.lastname') {
|
||||
$email_last_name.=str_replace(' ', '', $last_name);
|
||||
$email_prefix = $first_name.'.'.$email_last_name;
|
||||
|
||||
} elseif ($this->option('email_format')=='firstname') {
|
||||
$email_last_name.=str_replace(' ', '', $last_name);
|
||||
$email_prefix = $first_name;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
$user_username = $email_prefix;
|
||||
|
||||
// Generate an email based on their name if no email address is given
|
||||
if ($user_email=='') {
|
||||
if ($first_name=='Unknown') {
|
||||
$status_id = 7;
|
||||
}
|
||||
$email = strtolower($email_prefix).'@'.$this->option('domain');
|
||||
$user_email = str_replace("'", '', $email);
|
||||
}
|
||||
}
|
||||
|
||||
$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('License Name: '.$user_license_name);
|
||||
$this->comment('Serial No: '.$user_license_serial);
|
||||
$this->comment('Licensed To Name: '.$user_licensed_to_name);
|
||||
$this->comment('Licensed To Email: '.$user_licensed_to_email);
|
||||
$this->comment('Seats: '.$user_license_seats);
|
||||
$this->comment('Reassignable: '.$user_license_reassignable);
|
||||
$this->comment('Supplier: '.$user_license_supplier);
|
||||
$this->comment('Maintained: '.$user_license_maintained);
|
||||
$this->comment('Notes: '.$user_license_notes);
|
||||
$this->comment('Purchase Date: '.$user_license_purchase_date);
|
||||
$this->comment('Asset ID: '.$user_licensed_to_asset);
|
||||
|
||||
$this->comment('------------- Action Summary ----------------');
|
||||
|
||||
if ($user_username!='') {
|
||||
if ($user = User::where('username', $user_username)->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());
|
||||
}
|
||||
|
||||
$this->comment('User '.$first_name.' created');
|
||||
}
|
||||
} else {
|
||||
$user = new User;
|
||||
$user->user_id = null;
|
||||
}
|
||||
|
||||
|
||||
// Check for the supplier match and create it if it doesn't exist
|
||||
if ($supplier = Supplier::where('name', $user_license_supplier)->first()) {
|
||||
$this->comment('Supplier '.$user_license_supplier.' already exists');
|
||||
} else {
|
||||
$supplier = new Supplier();
|
||||
$supplier->name = e($user_license_supplier);
|
||||
$supplier->user_id = 1;
|
||||
|
||||
if ($supplier->save()) {
|
||||
$this->comment('Supplier '.$user_license_supplier.' was created');
|
||||
} else {
|
||||
$this->comment('Something went wrong! Supplier '.$user_license_supplier.' was NOT created');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Add the license
|
||||
$license = new License();
|
||||
$license->name = e($user_license_name);
|
||||
if ($user_license_purchase_date!='') {
|
||||
$license->purchase_date = $user_license_purchase_date;
|
||||
} else {
|
||||
$license->purchase_date = null;
|
||||
}
|
||||
$license->serial = e($user_license_serial);
|
||||
$license->seats = e($user_license_seats);
|
||||
$license->supplier_id = $supplier->id;
|
||||
$license->user_id = 1;
|
||||
if ($user_license_purchase_date!='') {
|
||||
$license->purchase_date = $user_license_purchase_date;
|
||||
} else {
|
||||
$license->purchase_date = null;
|
||||
}
|
||||
$license->license_name = $user_licensed_to_name;
|
||||
$license->license_email = $user_licensed_to_email;
|
||||
$license->notes = e($user_license_notes);
|
||||
|
||||
if ($license->save()) {
|
||||
$this->comment('License '.$user_license_name.' with serial number '.$user_license_serial.' was created');
|
||||
|
||||
|
||||
$license_seat_created = 0;
|
||||
|
||||
for ($x = 0; $x < $user_license_seats; $x++) {
|
||||
// Create the license seat entries
|
||||
$license_seat = new LicenseSeat();
|
||||
$license_seat->license_id = $license->id;
|
||||
|
||||
// Only assign the first seat to the user
|
||||
if ($x==0) {
|
||||
$license_seat->assigned_to = $user->id;
|
||||
} else {
|
||||
$license_seat->assigned_to = null;
|
||||
}
|
||||
|
||||
if($user_licensed_to_asset) {
|
||||
$asset = Asset::where('asset_tag', $user_licensed_to_asset)->first();
|
||||
if($asset) {
|
||||
$license_seat->asset_id = $asset->id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($license_seat->save()) {
|
||||
$license_seat_created++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($license_seat_created > 0) {
|
||||
$this->comment($license_seat_created.' seats were created');
|
||||
} else {
|
||||
$this->comment('Something went wrong! NO seats for '.$user_license_name.' were created');
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
$this->comment('Something went wrong! License '.$user_license_name.' was NOT created');
|
||||
}
|
||||
|
||||
|
||||
$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('domain', null, InputOption::VALUE_REQUIRED, 'Email domain for generated email addresses.', null),
|
||||
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('testrun', null, InputOption::VALUE_REQUIRED, 'Test the output without writing to the database or not.', null),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -80,10 +80,10 @@ class ObjectImportCommand extends Command
|
|||
$logFile = $this->option('logfile');
|
||||
\Log::useFiles($logFile);
|
||||
if ($this->option('testrun')) {
|
||||
$this->comment('====== TEST ONLY Asset Import for '.$filename.' ====');
|
||||
$this->comment('====== TEST ONLY Item Import for '.$filename.' ====');
|
||||
$this->comment('============== NO DATA WILL BE WRITTEN ==============');
|
||||
} else {
|
||||
$this->comment('======= Importing Assets from '.$filename.' =========');
|
||||
$this->comment('======= Importing Items from '.$filename.' =========');
|
||||
}
|
||||
$importer->import();
|
||||
|
||||
|
@ -175,7 +175,7 @@ class ObjectImportCommand extends Command
|
|||
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_NONE, 'If set, will parse and output data without adding to database', null),
|
||||
array('logfile', null, InputOption::VALUE_REQUIRED, 'The path to log output to. storage/logs/importer.log by default', storage_path('logs/importer.log') ),
|
||||
array('item-type', null, InputOption::VALUE_REQUIRED, 'Item Type To import. Valid Options are Asset, Consumable, Or Accessory', 'Asset'),
|
||||
array('item-type', null, InputOption::VALUE_REQUIRED, 'Item Type To import. Valid Options are Asset, Consumable, Accessory, License, or User', 'Asset'),
|
||||
array('web-importer', null, InputOption::VALUE_NONE, 'Internal: packages output for use with the web importer'),
|
||||
array('user_id', null, InputOption::VALUE_REQUIRED, 'ID of user creating items', 1),
|
||||
array('update', null, InputOption::VALUE_NONE, 'If a matching item is found, update item information'),
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use DB;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
|
@ -12,14 +9,18 @@ use App\Models\Category;
|
|||
use App\Models\Company;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\Department;
|
||||
use App\Models\Depreciation;
|
||||
use App\Models\Group;
|
||||
use App\Models\Import;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\Location;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PaveIt extends Command
|
||||
{
|
||||
|
@ -63,6 +64,7 @@ class PaveIt extends Command
|
|||
Company::getQuery()->delete();
|
||||
Component::getQuery()->delete();
|
||||
Consumable::getQuery()->delete();
|
||||
Department::getQuery()->delete();
|
||||
Depreciation::getQuery()->delete();
|
||||
License::getQuery()->delete();
|
||||
LicenseSeat::getQuery()->delete();
|
||||
|
@ -108,6 +110,7 @@ class PaveIt extends Command
|
|||
\DB::statement('drop table IF EXISTS custom_fields');
|
||||
\DB::statement('drop table IF EXISTS custom_fieldsets');
|
||||
\DB::statement('drop table IF EXISTS depreciations');
|
||||
\DB::statement('drop table IF EXISTS departments');
|
||||
\DB::statement('drop table IF EXISTS groups');
|
||||
\DB::statement('drop table IF EXISTS history');
|
||||
\DB::statement('drop table IF EXISTS components');
|
||||
|
|
|
@ -17,7 +17,6 @@ class Kernel extends ConsoleKernel
|
|||
Commands\CreateAdmin::class,
|
||||
Commands\SendExpirationAlerts::class,
|
||||
Commands\SendInventoryAlerts::class,
|
||||
Commands\LicenseImportCommand::class,
|
||||
Commands\ObjectImportCommand::class,
|
||||
Commands\Versioning::class,
|
||||
Commands\SystemBackup::class,
|
||||
|
|
|
@ -11,6 +11,7 @@ use App\Models\Import;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
|
||||
class ImportController extends Controller
|
||||
|
@ -29,7 +30,7 @@ class ImportController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* Process and store a CSV upload file.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
|
@ -65,17 +66,21 @@ class ImportController extends Controller
|
|||
$results['error'].= ' ' . $exception->getMessage();
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
|
||||
|
||||
}
|
||||
$file_name = date('Y-m-d-his').'-'.$fixed_filename;
|
||||
$import->file_path = $file_name;
|
||||
$import->filesize = filesize($path.'/'.$file_name);
|
||||
//TODO: is there a lighter way to do this?
|
||||
$reader = Reader::createFromPath("{$path}/{$file_name}");
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
$import->save();
|
||||
$results[] = $import;
|
||||
}
|
||||
$results = (new ImportsTransformer)->transformImports($results);
|
||||
return [
|
||||
'files' => $results
|
||||
'files' => $results,
|
||||
];
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 500);
|
||||
|
@ -90,7 +95,7 @@ class ImportController extends Controller
|
|||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$errors = $request->import(Import::find($import_id));
|
||||
$redirectTo = "hardware";
|
||||
$redirectTo = "hardware.index";
|
||||
switch ($request->get('import-type')) {
|
||||
case "asset":
|
||||
$redirectTo = "hardware.index";
|
||||
|
@ -107,6 +112,9 @@ class ImportController extends Controller
|
|||
case "license":
|
||||
$redirectTo = "licenses.index";
|
||||
break;
|
||||
case "user":
|
||||
$redirectTo = "users.index";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($errors) { //Failure
|
||||
|
|
|
@ -12,6 +12,7 @@ use App\Models\Asset;
|
|||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Import;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
|
@ -673,84 +674,6 @@ class AssetsController extends Controller
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Asset import upload page.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return View
|
||||
*/
|
||||
public function getImportUpload()
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
return view('hardware/import');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map the fields for import
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return View
|
||||
*/
|
||||
public function getImportMap()
|
||||
{
|
||||
|
||||
$this->authorize('create', Asset::class);
|
||||
|
||||
// This is currently hardcoded for testing - should use a post variable or something to dynamically select the correct file.
|
||||
$file = storage_path().'/private_uploads/imports/2017-06-08-072329-sample-assets.csv';
|
||||
$reader = Reader::createFromPath($file);
|
||||
$header_rows = $reader->fetchOne(0);
|
||||
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$first_row = $reader->fetchOne(1);
|
||||
|
||||
// Grab all of the custom fields to we can map those too.
|
||||
$custom_fields = CustomField::all();
|
||||
|
||||
return view('importer/fieldmapper')->with('header_rows', $header_rows)->with('first_row',$first_row)->with('custom_fields',$custom_fields);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the uploaded file
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function postProcessImportFile(ItemImportRequest $request)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$errors = $request->import();
|
||||
|
||||
// We use hardware instead of asset in the url
|
||||
$redirectTo = "hardware";
|
||||
switch (request('import-type')) {
|
||||
case "asset":
|
||||
$redirectTo = "hardware.index";
|
||||
break;
|
||||
case "accessory":
|
||||
$redirectTo = "accessories.index";
|
||||
break;
|
||||
case "consumable":
|
||||
$redirectTo = "consumables.index";
|
||||
break;
|
||||
case "component":
|
||||
$redirectTo = "components.index";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($errors) { //Failure
|
||||
return redirect()->back()->with('import_errors', json_decode(json_encode($errors)))->with('error', trans('admin/hardware/message.import.error'));
|
||||
}
|
||||
return redirect()->to(route($redirectTo))->with('success', trans('admin/hardware/message.import.success'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to clone an asset.
|
||||
*
|
||||
|
|
18
app/Http/Controllers/ImportsController.php
Normal file
18
app/Http/Controllers/ImportsController.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Transformers\ImportsTransformer;
|
||||
use App\Models\Import;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ImportsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$imports = Import::latest()->get();
|
||||
$imports = (new ImportsTransformer)->transformImports($imports);
|
||||
return view('importer/import')->with('imports', $imports);
|
||||
}
|
||||
}
|
|
@ -32,22 +32,35 @@ class ItemImportRequest extends FormRequest
|
|||
|
||||
public function import(Import $import)
|
||||
{
|
||||
ini_set('max_execution_time', 600); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', '500M');
|
||||
$filename = config('app.private_uploads') . '/imports/' . $import->file_path;
|
||||
$class = title_case($this->input('import-type'));
|
||||
$import->import_type = $this->input('import-type');
|
||||
$class = title_case($import->import_type);
|
||||
$classString = "App\\Importer\\{$class}Importer";
|
||||
$importer = new $classString($filename);
|
||||
$import->field_map = request('column-mappings');
|
||||
$import->save();
|
||||
$fieldMappings=[];
|
||||
if ($import->field_map) {
|
||||
// We submit as csv field: column, but the importer is happier if we flip it here.
|
||||
$fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER);
|
||||
// dd($fieldMappings);
|
||||
}
|
||||
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
|
||||
->setUserId(Auth::id())
|
||||
->setUpdating($this->has('import-update'))
|
||||
->setUsernameFormat('firstname.lastname');
|
||||
|
||||
->setUsernameFormat('firstname.lastname')
|
||||
->setFieldMappings($fieldMappings);
|
||||
// $logFile = storage_path('logs/importer.log');
|
||||
// \Log::useFiles($logFile);
|
||||
$importer->import();
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function log($string)
|
||||
{
|
||||
return; // FUTURE IMPLEMENTATION
|
||||
// \Log::Info($string);
|
||||
}
|
||||
|
||||
public function progress($count)
|
||||
|
@ -58,7 +71,6 @@ class ItemImportRequest extends FormRequest
|
|||
public function errorCallback($item, $field, $errorString)
|
||||
{
|
||||
$this->errors[$item->name][$field] = $errorString;
|
||||
// $this->errors[$item->name] = $errorString;
|
||||
}
|
||||
|
||||
private $errors;
|
||||
|
|
|
@ -26,6 +26,9 @@ class ImportsTransformer
|
|||
'name' => $import->name,
|
||||
'import_type' => $import->import_type,
|
||||
'created_at' => $import->created_at->diffForHumans(),
|
||||
'header_row' => $import->header_row,
|
||||
'first_row' => $import->first_row,
|
||||
'field_map' => $import->field_map,
|
||||
|
||||
];
|
||||
|
||||
|
|
|
@ -7,11 +7,9 @@ use App\Models\Accessory;
|
|||
|
||||
class AccessoryImporter extends ItemImporter
|
||||
{
|
||||
protected $accessories;
|
||||
public function __construct($filename)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
$this->accessories = Accessory::all();
|
||||
}
|
||||
|
||||
protected function handle($row)
|
||||
|
@ -28,17 +26,14 @@ class AccessoryImporter extends ItemImporter
|
|||
*/
|
||||
public function createAccessoryIfNotExists()
|
||||
{
|
||||
$accessoryId = $this->accessories->search(function ($key) {
|
||||
return strcasecmp($key->name, $this->item['name']) == 0;
|
||||
});
|
||||
if ($accessoryId !== false) {
|
||||
$accessory = Accessory::where('name', $this->item['name'])->first();
|
||||
if ($accessory) {
|
||||
if (!$this->updating) {
|
||||
$this->log('A matching Accessory ' . $this->item["name"] . ' already exists. ');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log('Updating Accessory');
|
||||
$accessory = $this->accessories[$accessoryId];
|
||||
$accessory->update($this->sanitizeItemForUpdating($accessory));
|
||||
if (!$this->testRun) {
|
||||
$accessory->save();
|
||||
|
@ -57,7 +52,8 @@ class AccessoryImporter extends ItemImporter
|
|||
if ($accessory->save()) {
|
||||
$accessory->logCreate('Imported using CSV Importer');
|
||||
$this->log('Accessory ' . $this->item["name"] . ' was created');
|
||||
return;
|
||||
}
|
||||
$this->jsonError($accessory, 'Accessory');
|
||||
$this->logError($accessory, 'Accessory');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,10 +45,11 @@ class AssetImporter extends ItemImporter
|
|||
public function createAssetIfNotExists(array $row)
|
||||
{
|
||||
$editingAsset = false;
|
||||
$asset = Asset::where(['asset_tag'=> $this->item['asset_tag']])->first();
|
||||
$asset_tag = $this->findCsvMatch($row, "asset_tag");
|
||||
$asset = Asset::where(['asset_tag'=> $asset_tag])->first();
|
||||
if ($asset) {
|
||||
if (!$this->updating) {
|
||||
$this->log('A matching Asset ' . $this->item['asset_tag'] . ' already exists');
|
||||
$this->log('A matching Asset ' . $asset_tag . ' already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -58,24 +59,19 @@ class AssetImporter extends ItemImporter
|
|||
$this->log("No Matching Asset, Creating a new one");
|
||||
$asset = new Asset;
|
||||
}
|
||||
$this->item['serial'] = $this->array_smart_fetch($row, "serial number");
|
||||
$this->item['image'] = $this->array_smart_fetch($row, "image");
|
||||
$this->item['warranty_months'] = intval($this->array_smart_fetch($row, "warranty months"));
|
||||
if ($this->item['asset_model'] = $this->createOrFetchAssetModel($row)) {
|
||||
$this->item['model_id'] = $this->item['asset_model']->id;
|
||||
}
|
||||
if (isset($this->item["status_label"])) {
|
||||
$this->item['status_id'] = $this->item["status_label"]->id;
|
||||
} elseif (!$editingAsset) {
|
||||
// Assume if we are editing, we already have a status and can ignore.
|
||||
|
||||
$this->item['image'] = $this->findCsvMatch($row, "image");
|
||||
$this->item['warranty_months'] = intval($this->findCsvMatch($row, "warranty months"));
|
||||
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
|
||||
if (!$this->item['status_id'] && !$editingAsset) {
|
||||
$this->log("No status field found, defaulting to first status.");
|
||||
$this->item['status_id'] = $this->defaultStatusLabelId;
|
||||
}
|
||||
|
||||
|
||||
$this->item['asset_tag'] = $asset_tag;
|
||||
// By default we're set this to location_id in the item.
|
||||
$item = $this->sanitizeItemForStoring($asset, $editingAsset);
|
||||
if (isset($this->item["location"])) {
|
||||
if (isset($this->item["location_id"])) {
|
||||
$item['rtd_location_id'] = $this->item['location_id'];
|
||||
unset($item['location_id']);
|
||||
}
|
||||
|
@ -95,7 +91,7 @@ class AssetImporter extends ItemImporter
|
|||
$this->log('Asset ' . $this->item["name"] . ' with serial number ' . $this->item['serial'] . ' was created');
|
||||
return;
|
||||
}
|
||||
$this->jsonError($asset, 'Asset "' . $this->item['name'].'"');
|
||||
$this->logError($asset, 'Asset "' . $this->item['name'].'"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,9 @@ use App\Models\Component;
|
|||
|
||||
class ComponentImporter extends ItemImporter
|
||||
{
|
||||
protected $components;
|
||||
public function __construct($filename)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
$this->components = Component::all();
|
||||
}
|
||||
|
||||
protected function handle($row)
|
||||
|
@ -31,11 +29,9 @@ class ComponentImporter extends ItemImporter
|
|||
$component = null;
|
||||
$editingComponent = false;
|
||||
$this->log("Creating Component");
|
||||
$componentId = $this->components->search(function ($key) {
|
||||
return strcasecmp($key->name, $this->item['name']) == 0;
|
||||
});
|
||||
$component = Component::where('name', $this->item['name']);
|
||||
|
||||
if ($componentId !== false) {
|
||||
if ($component) {
|
||||
$editingComponent = true;
|
||||
$this->log('A matching Component ' . $this->item["name"] . ' already exists. ');
|
||||
if (!$this->updating) {
|
||||
|
@ -75,6 +71,6 @@ class ComponentImporter extends ItemImporter
|
|||
}
|
||||
return;
|
||||
}
|
||||
$this->jsonError($component, 'Component');
|
||||
$this->logError($component, 'Component');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,9 @@ use App\Models\Consumable;
|
|||
|
||||
class ConsumableImporter extends ItemImporter
|
||||
{
|
||||
protected $consumables;
|
||||
public function __construct($filename)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
$this->consumables = Consumable::all();
|
||||
}
|
||||
|
||||
protected function handle($row)
|
||||
|
@ -28,16 +26,13 @@ class ConsumableImporter extends ItemImporter
|
|||
*/
|
||||
public function createConsumableIfNotExists()
|
||||
{
|
||||
$consumableId = $this->consumables->search(function ($key) {
|
||||
return strcasecmp($key->name, $this->item['name']) == 0;
|
||||
});
|
||||
if ($consumableId !== false) {
|
||||
$consumable = Consumable::where('name', $this->item['name'])->first();
|
||||
if ($consumable) {
|
||||
if (!$this->updating) {
|
||||
$this->log('A matching Consumable ' . $this->item["name"] . ' already exists. ');
|
||||
return;
|
||||
}
|
||||
$this->log('Updating Consumable');
|
||||
$consumable = $this->consumables[$consumableId];
|
||||
$consumable->update($this->sanitizeItemForUpdating($consumable));
|
||||
if (!$this->testRun) {
|
||||
$consumable->save();
|
||||
|
@ -57,7 +52,7 @@ class ConsumableImporter extends ItemImporter
|
|||
$this->log("Consumable " . $this->item["name"] . ' was created');
|
||||
return;
|
||||
}
|
||||
$this->jsonError($consumable, 'Consumable');
|
||||
$this->logError($consumable, 'Consumable');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ abstract class Importer
|
|||
* @var bool
|
||||
*/
|
||||
protected $updating;
|
||||
/**
|
||||
* Map of item fields->csv names
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldMap = [];
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
|
@ -71,7 +76,7 @@ abstract class Importer
|
|||
|
||||
public function import()
|
||||
{
|
||||
$results = $this->normalizeInputArray($this->csv->fetchAssoc());
|
||||
$results = $this->csv->fetchAssoc();
|
||||
$this->customFields = CustomField::All(['name']);
|
||||
DB::transaction(function () use (&$results) {
|
||||
Model::unguard();
|
||||
|
@ -87,20 +92,6 @@ abstract class Importer
|
|||
|
||||
abstract protected function handle($row);
|
||||
|
||||
/**
|
||||
* @param $results
|
||||
* @return array
|
||||
*/
|
||||
public function normalizeInputArray($results)
|
||||
{
|
||||
$newArray = [];
|
||||
|
||||
foreach ($results as $index => $arrayToNormalize) {
|
||||
$newArray[$index] = array_change_key_case($arrayToNormalize);
|
||||
}
|
||||
return $newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the given key exists in the array, and trim excess white space before returning it
|
||||
*
|
||||
|
@ -111,17 +102,38 @@ abstract class Importer
|
|||
* @param $default string
|
||||
* @return string
|
||||
*/
|
||||
public function array_smart_fetch(array $array, $key, $default = '')
|
||||
public function findCsvMatch(array $array, $key, $default = '')
|
||||
{
|
||||
$val = $default;
|
||||
if (array_key_exists(trim($key), $array)) {
|
||||
// dd($array);
|
||||
if($customKey = $this->lookupCustomKey($key)) {
|
||||
$key = $customKey;
|
||||
}
|
||||
$this->log("Custom Key: ${key}");
|
||||
if (array_key_exists($key, $array)) {
|
||||
$val = e(Encoding::toUTF8(trim($array[ $key ])));
|
||||
}
|
||||
$key = title_case($key);
|
||||
// $this->log("${key}: ${val}");
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up A custom key in the custom field map
|
||||
*
|
||||
* @author Daniel Melzter
|
||||
* @since 4.0
|
||||
* @param $key string
|
||||
* @return string|null
|
||||
*/
|
||||
public function lookupCustomKey($key)
|
||||
{
|
||||
// dd($this->fieldMap);
|
||||
if (array_key_exists($key, $this->fieldMap)) {
|
||||
// $this->log("Found a match in our custom map: {$key} is " . $this->fieldMap[$key]);
|
||||
return $key = $this->fieldMap[$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Figure out the fieldname of the custom field
|
||||
*
|
||||
|
@ -141,7 +153,7 @@ abstract class Importer
|
|||
call_user_func($this->logCallback, $string);
|
||||
}
|
||||
|
||||
protected function jsonError($item, $field)
|
||||
protected function logError($item, $field)
|
||||
{
|
||||
call_user_func($this->errorCallback, $item, $field, $item->getErrors());
|
||||
}
|
||||
|
@ -160,9 +172,9 @@ abstract class Importer
|
|||
*/
|
||||
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");
|
||||
$user_name = $this->findCsvMatch($row, "name");
|
||||
$user_email = $this->findCsvMatch($row, "email");
|
||||
$user_username = $this->findCsvMatch($row, "username");
|
||||
$first_name = '';
|
||||
$last_name = '';
|
||||
// A number was given instead of a name
|
||||
|
@ -214,7 +226,7 @@ abstract class Importer
|
|||
if ($user->save()) {
|
||||
$this->log('User '.$first_name.' created');
|
||||
} else {
|
||||
$this->jsonError($user, 'User "' . $first_name . '"');
|
||||
$this->logError($user, 'User "' . $first_name . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -279,6 +291,22 @@ abstract class Importer
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines mappings of csv fields
|
||||
*
|
||||
* @param bool $updating the updating
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFieldMappings($fields)
|
||||
{
|
||||
// Some initial sanitization.
|
||||
|
||||
$this->fieldMap = $fields;
|
||||
$this->log($this->fieldMap);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callbacks for the import
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Importer;
|
||||
|
||||
use App\Importer\UserImporter;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
|
@ -20,61 +21,58 @@ class ItemImporter extends Importer
|
|||
|
||||
protected function handle($row)
|
||||
{
|
||||
// TODO: CHeck if this interferes with checkout to user.. it shouldn't..
|
||||
$this->item["user_id"] = $this->user_id;
|
||||
$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");
|
||||
$item_supplier = $this->array_smart_fetch($row, "supplier");
|
||||
$this->item["name"] = $this->array_smart_fetch($row, "item name");
|
||||
$this->item["purchase_date"] = null;
|
||||
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")));
|
||||
}
|
||||
|
||||
$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["qty"] = $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");
|
||||
|
||||
if ($this->item["user"] = $this->createOrFetchUser($row)) {
|
||||
$this->item['assigned_to'] = $this->item['user']->id;
|
||||
}
|
||||
|
||||
if ($this->shouldUpdateField($item_location)) {
|
||||
if ($this->item["location"] = $this->createOrFetchLocation($item_location)) {
|
||||
$this->item["location_id"] = $this->item["location"]->id;
|
||||
}
|
||||
}
|
||||
$item_category = $this->findCsvMatch($row, "category");
|
||||
if ($this->shouldUpdateField($item_category)) {
|
||||
if ($this->item["category"] = $this->createOrFetchCategory($item_category)) {
|
||||
$this->item["category_id"] = $this->item["category"]->id;
|
||||
}
|
||||
}
|
||||
if ($this->shouldUpdateField($item_manufacturer)) {
|
||||
if ($this->item["manufacturer"] = $this->createOrFetchManufacturer($item_manufacturer)) {
|
||||
$this->item["manufacturer_id"] = $this->item["manufacturer"]->id;
|
||||
}
|
||||
$this->item["category_id"] = $this->createOrFetchCategory($item_category);
|
||||
}
|
||||
|
||||
$item_company_name = $this->findCsvMatch($row, "company");
|
||||
if ($this->shouldUpdateField($item_company_name)) {
|
||||
if ($this->item["company"] = $this->createOrFetchCompany($item_company_name)) {
|
||||
$this->item["company_id"] = $this->item["company"]->id;
|
||||
}
|
||||
$this->item["company_id"] = $this->createOrFetchCompany($item_company_name);
|
||||
}
|
||||
|
||||
$item_location = $this->findCsvMatch($row, "location");
|
||||
if ($this->shouldUpdateField($item_location)) {
|
||||
$this->item["location_id"] = $this->createOrFetchLocation($item_location);
|
||||
}
|
||||
|
||||
$item_manufacturer = $this->findCsvMatch($row, "manufacturer");
|
||||
if ($this->shouldUpdateField($item_manufacturer)) {
|
||||
$this->item["manufacturer_id"] = $this->createOrFetchManufacturer($item_manufacturer);
|
||||
}
|
||||
|
||||
$item_status_name = $this->findCsvMatch($row, "status");
|
||||
if ($this->shouldUpdateField($item_status_name)) {
|
||||
if ($this->item["status_label"] = $this->createOrFetchStatusLabel($item_status_name)) {
|
||||
$this->item["status_label_id"] = $this->item["status_label"]->id;
|
||||
}
|
||||
$this->item["status_id"] = $this->createOrFetchStatusLabel($item_status_name);
|
||||
}
|
||||
|
||||
$item_supplier = $this->findCsvMatch($row, "supplier");
|
||||
if ($this->shouldUpdateField($item_supplier)) {
|
||||
if ($this->item['supplier'] = $this->createOrFetchSupplier($item_supplier)) {
|
||||
$this->item['supplier_id'] = $this->item['supplier']->id;
|
||||
$this->item['supplier_id'] = $this->createOrFetchSupplier($item_supplier);
|
||||
}
|
||||
|
||||
$this->item["name"] = $this->findCsvMatch($row, "item_name");
|
||||
$this->item["notes"] = $this->findCsvMatch($row, "notes");
|
||||
$this->item["order_number"] = $this->findCsvMatch($row, "order_number");
|
||||
$this->item["purchase_cost"] = $this->findCsvMatch($row, "purchase_cost");
|
||||
|
||||
$this->item["purchase_date"] = null;
|
||||
if ($this->findCsvMatch($row, "purchase date")!='') {
|
||||
$this->item["purchase_date"] = date("Y-m-d 00:00:01", strtotime($this->findCsvMatch($row, "purchase date")));
|
||||
}
|
||||
|
||||
$this->item["qty"] = $this->findCsvMatch($row, "quantity");
|
||||
$this->item["requestable"] = $this->findCsvMatch($row, "requestable");
|
||||
$this->item["user_id"] = $this->user_id;
|
||||
$this->item['serial'] = $this->findCsvMatch($row, "serial number");
|
||||
// NO need to call this method if we're running the user import.
|
||||
// TODO: Merge these methods.
|
||||
if(get_class($this) !== UserImporter::class) {
|
||||
if ($this->item["user"] = $this->createOrFetchUser($row)) {
|
||||
$this->item['assigned_to'] = $this->item['user']->id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,8 +92,8 @@ class ItemImporter extends Importer
|
|||
// Create a collection for all manipulations to come.
|
||||
$item = collect($this->item);
|
||||
// First Filter the item down to the model's fillable fields
|
||||
|
||||
$item = $item->only($model->getFillable());
|
||||
|
||||
// Then iterate through the item and, if we are updating, remove any blank values.
|
||||
if ($updating) {
|
||||
$item = $item->reject(function ($value) {
|
||||
|
@ -108,8 +106,8 @@ class ItemImporter extends Importer
|
|||
|
||||
/**
|
||||
* Convenience function for updating that strips the empty values.
|
||||
*
|
||||
*
|
||||
* @param $model SnipeModel Model that's being updated.
|
||||
* @return array
|
||||
*/
|
||||
protected function sanitizeItemForUpdating($model)
|
||||
{
|
||||
|
@ -142,13 +140,13 @@ class ItemImporter extends Importer
|
|||
* @param array
|
||||
* @param $category Category
|
||||
* @param $manufacturer Manufacturer
|
||||
* @return AssetModel
|
||||
* @return int Id of asset model created/found
|
||||
* @internal param $asset_modelno string
|
||||
*/
|
||||
public function createOrFetchAssetModel(array $row)
|
||||
{
|
||||
$asset_model_name = $this->array_smart_fetch($row, "model name");
|
||||
$asset_modelNumber = $this->array_smart_fetch($row, "model number");
|
||||
$asset_model_name = $this->findCsvMatch($row, "asset_model");
|
||||
$asset_modelNumber = $this->findCsvMatch($row, "model_number");
|
||||
// TODO: At the moment, this means we can't update the model number if the model name stays the same.
|
||||
if (!$this->shouldUpdateField($asset_model_name)) {
|
||||
return;
|
||||
|
@ -162,10 +160,9 @@ class ItemImporter extends Importer
|
|||
$asset_model = AssetModel::where(['name' => $asset_model_name, 'model_number' => $asset_modelNumber])->first();
|
||||
|
||||
if ($asset_model) {
|
||||
|
||||
if (!$this->updating) {
|
||||
$this->log("A matching model already exists, returning it.");
|
||||
return $asset_model;
|
||||
return $asset_model->id;
|
||||
}
|
||||
$this->log("Matching Model found, updating it.");
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
|
@ -175,7 +172,8 @@ class ItemImporter extends Importer
|
|||
if (!$this->testRun) {
|
||||
$asset_model->save();
|
||||
}
|
||||
return $asset_model;
|
||||
$this->log("Asset Model Updated");
|
||||
return $asset_model->id;
|
||||
}
|
||||
$this->log("No Matching Model, Creating a new one");
|
||||
|
||||
|
@ -185,18 +183,18 @@ class ItemImporter extends Importer
|
|||
$item['model_number'] = $asset_modelNumber;
|
||||
|
||||
$asset_model->fill($item);
|
||||
|
||||
$item = null;
|
||||
if ($this->testRun) {
|
||||
$this->log('TEST RUN - asset_model ' . $asset_model->name . ' not created');
|
||||
return $asset_model;
|
||||
return $asset_model->id;
|
||||
}
|
||||
|
||||
if ($asset_model->save()) {
|
||||
$this->log('Asset Model ' . $asset_model_name . ' with model number ' . $asset_modelNumber . ' was created');
|
||||
return $asset_model;
|
||||
return $asset_model->id;
|
||||
}
|
||||
$this->jsonError($asset_model, 'Asset Model "' . $asset_model_name . '"');
|
||||
return;
|
||||
$this->logError($asset_model, 'Asset Model "' . $asset_model_name . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,7 +203,7 @@ class ItemImporter extends Importer
|
|||
* @author Daniel Melzter
|
||||
* @since 3.0
|
||||
* @param $asset_category string
|
||||
* @return Category
|
||||
* @return int Id of category created/found
|
||||
* @internal param string $item_type
|
||||
*/
|
||||
public function createOrFetchCategory($asset_category)
|
||||
|
@ -213,6 +211,7 @@ class ItemImporter extends Importer
|
|||
// Magic to transform "AssetImporter" to "asset" or similar.
|
||||
$classname = class_basename(get_class($this));
|
||||
$item_type = strtolower(substr($classname, 0, strpos($classname, 'Importer')));
|
||||
|
||||
if (empty($asset_category)) {
|
||||
$asset_category = 'Unnamed Category';
|
||||
}
|
||||
|
@ -220,7 +219,7 @@ class ItemImporter extends Importer
|
|||
|
||||
if ($category) {
|
||||
$this->log("A matching category: " . $asset_category . " already exists");
|
||||
return $category;
|
||||
return $category->id;
|
||||
}
|
||||
|
||||
$category = new Category();
|
||||
|
@ -229,14 +228,15 @@ class ItemImporter extends Importer
|
|||
$category->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
return $category;
|
||||
return $category->id;
|
||||
}
|
||||
if ($category->save()) {
|
||||
$this->log('Category ' . $asset_category . ' was created');
|
||||
return $category;
|
||||
return $category->id;
|
||||
}
|
||||
|
||||
$this->jsonError($category, 'Category "'. $asset_category. '"');
|
||||
$this->logError($category, 'Category "'. $asset_category. '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,28 +245,28 @@ class ItemImporter extends Importer
|
|||
* @author Daniel Melzter
|
||||
* @since 3.0
|
||||
* @param $asset_company_name string
|
||||
* @return Company
|
||||
* @return int id of company created/found
|
||||
*/
|
||||
public function createOrFetchCompany($asset_company_name)
|
||||
{
|
||||
$company = Company::where(['name' => $asset_company_name])->first();
|
||||
if ($company) {
|
||||
$this->log('A matching Company ' . $asset_company_name . ' already exists');
|
||||
return $company;
|
||||
return $company->id;
|
||||
}
|
||||
$company = new Company();
|
||||
$company->name = $asset_company_name;
|
||||
|
||||
if ($this->testRun) {
|
||||
|
||||
return $company;
|
||||
return $company->id;
|
||||
}
|
||||
if ($company->save()) {
|
||||
$this->log('Company ' . $asset_company_name . ' was created');
|
||||
return $company;
|
||||
return $company->id;
|
||||
}
|
||||
$this->jsonError($company, 'Company');
|
||||
return;
|
||||
$this->logError($company, 'Company');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,7 +286,7 @@ class ItemImporter extends Importer
|
|||
|
||||
if ($status) {
|
||||
$this->log('A matching Status ' . $asset_statuslabel_name . ' already exists');
|
||||
return $status;
|
||||
return $status->id;
|
||||
}
|
||||
$this->log("Creating a new status");
|
||||
$status = new Statuslabel();
|
||||
|
@ -297,16 +297,16 @@ class ItemImporter extends Importer
|
|||
$status->archived = 0;
|
||||
|
||||
if ($this->testRun) {
|
||||
return $status;
|
||||
return $status->id;
|
||||
}
|
||||
|
||||
if ($status->save()) {
|
||||
$this->log('Status ' . $asset_statuslabel_name . ' was created');
|
||||
return $status;
|
||||
return $status->id;
|
||||
}
|
||||
|
||||
$this->jsonError($status, 'Status "'. $asset_statuslabel_name . '"');
|
||||
return;
|
||||
$this->logError($status, 'Status "'. $asset_statuslabel_name . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -328,7 +328,7 @@ class ItemImporter extends Importer
|
|||
|
||||
if ($manufacturer) {
|
||||
$this->log('Manufacturer ' . $item_manufacturer . ' already exists') ;
|
||||
return $manufacturer;
|
||||
return $manufacturer->id;
|
||||
}
|
||||
|
||||
//Otherwise create a manufacturer.
|
||||
|
@ -337,14 +337,14 @@ class ItemImporter extends Importer
|
|||
$manufacturer->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
return $manufacturer;
|
||||
return $manufacturer->id;
|
||||
}
|
||||
if ($manufacturer->save()) {
|
||||
$this->log('Manufacturer ' . $manufacturer->name . ' was created');
|
||||
return $manufacturer;
|
||||
return $manufacturer->id;
|
||||
}
|
||||
$this->jsonError($manufacturer, 'Manufacturer "'. $manufacturer->name . '"');
|
||||
return;
|
||||
$this->logError($manufacturer, 'Manufacturer "'. $manufacturer->name . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -363,9 +363,9 @@ class ItemImporter extends Importer
|
|||
}
|
||||
$location = Location::where(['name' => $asset_location])->first();
|
||||
|
||||
if ($location !== false) {
|
||||
if ($location) {
|
||||
$this->log('Location ' . $asset_location . ' already exists');
|
||||
return $location;
|
||||
return $location->id;
|
||||
}
|
||||
// No matching locations in the collection, create a new one.
|
||||
$location = new Location();
|
||||
|
@ -377,14 +377,14 @@ class ItemImporter extends Importer
|
|||
$location->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
return $location;
|
||||
return $location->id;
|
||||
}
|
||||
if ($location->save()) {
|
||||
$this->log('Location ' . $asset_location . ' was created');
|
||||
return $location;
|
||||
return $location->id;
|
||||
}
|
||||
$this->jsonError($location, 'Location');
|
||||
return;
|
||||
$this->logError($location, 'Location');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -405,7 +405,7 @@ class ItemImporter extends Importer
|
|||
|
||||
if ($supplier) {
|
||||
$this->log('Supplier ' . $item_supplier . ' already exists');
|
||||
return $supplier;
|
||||
return $supplier->id;
|
||||
}
|
||||
|
||||
$supplier = new Supplier();
|
||||
|
@ -413,13 +413,13 @@ class ItemImporter extends Importer
|
|||
$supplier->user_id = $this->user_id;
|
||||
|
||||
if ($this->testRun) {
|
||||
return $supplier;
|
||||
return $supplier->id;
|
||||
}
|
||||
if ($supplier->save()) {
|
||||
$this->log('Supplier ' . $item_supplier . ' was created');
|
||||
return $supplier;
|
||||
return $supplier->id;
|
||||
}
|
||||
$this->jsonError($supplier, 'Supplier');
|
||||
return;
|
||||
$this->logError($supplier, 'Supplier');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,11 +36,9 @@ class LicenseImporter extends ItemImporter
|
|||
public function createLicenseIfNotExists(array $row)
|
||||
{
|
||||
$editingLicense = false;
|
||||
$license = new License;
|
||||
$license_id = $this->licenses->search(function ($key) {
|
||||
return strcasecmp($key->name, $this->item['name']) == 0;
|
||||
});
|
||||
if ($license_id !== false) {
|
||||
$license = License::where('name', $this->item['name'])->first();
|
||||
|
||||
if ($license) {
|
||||
if (!$this->updating) {
|
||||
$this->log('A matching License ' . $this->item['name'] . ' already exists');
|
||||
return;
|
||||
|
@ -48,30 +46,26 @@ class LicenseImporter extends ItemImporter
|
|||
|
||||
$this->log("Updating License");
|
||||
$editingLicense = true;
|
||||
$license = $this->licenses[$license_id];
|
||||
} else {
|
||||
$this->log("No Matching License, Creating a new one");
|
||||
}
|
||||
|
||||
$asset_tag = $this->item['asset_tag'] = $this->array_smart_fetch($row, 'asset_tag'); // used for checkout out to an asset.
|
||||
$this->item['expiration_date'] = $this->array_smart_fetch($row, 'expiration date');
|
||||
$this->item['license_email'] = $this->array_smart_fetch($row, "licensed to email");
|
||||
$this->item['license_name'] = $this->array_smart_fetch($row, "licensed to name");
|
||||
$this->item['maintained'] = $this->array_smart_fetch($row, 'maintained');
|
||||
$this->item['purchase_order'] = $this->array_smart_fetch($row, 'purchase_order');
|
||||
$this->item['reassignable'] = $this->array_smart_fetch($row, 'reassignable');
|
||||
$this->item['serial'] = $this->array_smart_fetch($row, "serial number");
|
||||
$this->item['termination_date'] = $this->array_smart_fetch($row, 'termination_date');
|
||||
$this->item['seats'] = $this->array_smart_fetch($row, 'seats');
|
||||
$license = new License;
|
||||
$asset_tag = $this->item['asset_tag'] = $this->findCsvMatch($row, 'asset_tag'); // used for checkout out to an asset.
|
||||
$this->item['expiration_date'] = $this->findCsvMatch($row, 'expiration_date');
|
||||
$this->item['license_email'] = $this->findCsvMatch($row, "licensed_to_email");
|
||||
$this->item['license_name'] = $this->findCsvMatch($row, "licensed_to_name");
|
||||
$this->item['maintained'] = $this->findCsvMatch($row, 'maintained');
|
||||
$this->item['purchase_order'] = $this->findCsvMatch($row, 'purchase_order');
|
||||
$this->item['reassignable'] = $this->findCsvMatch($row, 'reassignable');
|
||||
$this->item['seats'] = $this->findCsvMatch($row, 'seats');
|
||||
$this->item['termination_date'] = $this->findCsvMatch($row, 'termination_date');
|
||||
|
||||
if ($editingLicense) {
|
||||
$license->update($this->sanitizeItemForUpdating($license));
|
||||
} else {
|
||||
$license->fill($this->sanitizeItemForStoring($license));
|
||||
}
|
||||
if (!$editingLicense) {
|
||||
$this->licenses->add($license);
|
||||
}
|
||||
|
||||
if (!$this->testRun) {
|
||||
if ($license->save()) {
|
||||
$license->logCreate('Imported using csv importer');
|
||||
|
@ -95,7 +89,7 @@ class LicenseImporter extends ItemImporter
|
|||
}
|
||||
return;
|
||||
}
|
||||
$this->jsonError($license, 'License "' . $this->item['name'].'"');
|
||||
$this->logError($license, 'License "' . $this->item['name'].'"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
73
app/Importer/UserImporter.php
Normal file
73
app/Importer/UserImporter.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace App\Importer;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\User;
|
||||
|
||||
class UserImporter extends ItemImporter
|
||||
{
|
||||
protected $users;
|
||||
public function __construct($filename)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
// $this->users = User::all();
|
||||
}
|
||||
|
||||
protected function handle($row)
|
||||
{
|
||||
parent::handle($row);
|
||||
$this->createUserIfNotExists($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user if a duplicate does not exist.
|
||||
* @todo Investigate how this should interact with Importer::createOrFetchUser
|
||||
*
|
||||
* @author Daniel Melzter
|
||||
* @since 4.0
|
||||
*/
|
||||
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, 'user_email');
|
||||
$this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
|
||||
$this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle');
|
||||
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
|
||||
$this->item['password'] = $this->tempPassword;
|
||||
$user = User::where('username', $this->item['username'])->first();
|
||||
if ($user) {
|
||||
if (!$this->updating) {
|
||||
$this->log('A matching User ' . $this->item["name"] . ' already exists. ');
|
||||
return;
|
||||
}
|
||||
$this->log('Updating User');
|
||||
// $user = $this->users[$userId];
|
||||
$user->update($this->sanitizeItemForUpdating($user));
|
||||
if (!$this->testRun) {
|
||||
$user->save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->log("No matching user, creating one");
|
||||
$user = new User();
|
||||
$user->fill($this->sanitizeItemForStoring($user));
|
||||
|
||||
if ($this->testRun) {
|
||||
$this->log('TEST RUN - User ' . $this->item['name'] . ' not created');
|
||||
return;
|
||||
}
|
||||
if ($user->save()) {
|
||||
// $user->logCreate('Imported using CSV Importer');
|
||||
$this->log("User " . $this->item["name"] . ' was created');
|
||||
$user = null;
|
||||
$this->item = null;
|
||||
return;
|
||||
}
|
||||
$this->logError($user, 'User');
|
||||
return;
|
||||
}
|
||||
}
|
33
app/Importer/import_mappings.md
Normal file
33
app/Importer/import_mappings.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
| CSV | Item | Applicable Types |
|
||||
|---------------------|------------------|-------------------------------------------|
|
||||
| asset tag | asset_tag | Asset |
|
||||
| category | category_id | All |
|
||||
| company | company_id | All |
|
||||
| item name | name | All |
|
||||
| image | image | asset |
|
||||
| expiration date | expiration_date | License |
|
||||
| location | location_id | All |
|
||||
| notes | notes | All |
|
||||
| licensed to email | license_email | License |
|
||||
| licensed to name | license_name | License |
|
||||
| maintained | maintained | License |
|
||||
| manufacturer | manufacturer_id | All |
|
||||
| model name | model_id | Asset |
|
||||
| model number | model_id | Asset |
|
||||
| order number | order_number | All ? |
|
||||
| purchase cost | purchase_cost | All ? |
|
||||
| purchase date | purchase_date | All ? |
|
||||
| purchase order | purchase_order | License |
|
||||
| quantity | qty | Accessory, Consumable, Component, License |
|
||||
| reassignable | reassignable | License |
|
||||
| requestable | requestable | Asset, Accessory? |
|
||||
| seats | seats | License |
|
||||
| serial number | serial | asset, license |
|
||||
| status | status_id | asset ? All |
|
||||
| supplier | supplier_id | Asset ? All |
|
||||
| termination date | termination_date | License |
|
||||
| warranty months | warranty_months | asset |
|
||||
| User Related Fields | assigned_to | Asset |
|
||||
| name | | |
|
||||
| email | | |
|
||||
| username | | |
|
|
@ -6,4 +6,9 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Import extends Model
|
||||
{
|
||||
protected $casts = [
|
||||
'header_row' => 'array',
|
||||
'first_row' => 'array',
|
||||
'field_map' => 'json'
|
||||
];
|
||||
}
|
||||
|
|
|
@ -24,7 +24,19 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
|
|||
protected $hidden = ['password'];
|
||||
protected $table = 'users';
|
||||
protected $injectUniqueIdentifier = true;
|
||||
protected $fillable = ['first_name', 'last_name', 'email','password','username','department_id'];
|
||||
protected $fillable = [
|
||||
'email',
|
||||
'last_name',
|
||||
'company_id',
|
||||
'department_id',
|
||||
'employee_num',
|
||||
'jobtitle',
|
||||
'location_id',
|
||||
'password',
|
||||
'phone_number',
|
||||
'username',
|
||||
'first_name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'activated' => 'boolean',
|
||||
|
|
|
@ -24,8 +24,6 @@ class AccessoryObserver
|
|||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('update');
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +39,6 @@ class AccessoryObserver
|
|||
{
|
||||
$settings = Setting::first();
|
||||
$settings->increment('next_auto_tag_base');
|
||||
\Log::debug('Setting new next_auto_tag_base value');
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Accessory::class;
|
||||
|
|
|
@ -17,15 +17,12 @@ class AssetObserver
|
|||
*/
|
||||
public function updated(Asset $asset)
|
||||
{
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Asset::class;
|
||||
$logAction->item_id = $asset->id;
|
||||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('update');
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +38,6 @@ class AssetObserver
|
|||
{
|
||||
$settings = Setting::first();
|
||||
$settings->increment('next_auto_tag_base');
|
||||
\Log::debug('Setting new next_auto_tag_base value');
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Asset::class;
|
||||
|
|
|
@ -24,8 +24,6 @@ class ComponentObserver
|
|||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('update');
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +39,6 @@ class ComponentObserver
|
|||
{
|
||||
$settings = Setting::first();
|
||||
$settings->increment('next_auto_tag_base');
|
||||
\Log::debug('Setting new next_auto_tag_base value');
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Component::class;
|
||||
|
|
|
@ -24,8 +24,6 @@ class ConsumableObserver
|
|||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('update');
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +39,6 @@ class ConsumableObserver
|
|||
{
|
||||
$settings = Setting::first();
|
||||
$settings->increment('next_auto_tag_base');
|
||||
\Log::debug('Setting new next_auto_tag_base value');
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Consumable::class;
|
||||
|
|
|
@ -24,8 +24,6 @@ class LicenseObserver
|
|||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('update');
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +39,6 @@ class LicenseObserver
|
|||
{
|
||||
$settings = Setting::first();
|
||||
$settings->increment('next_auto_tag_base');
|
||||
\Log::debug('Setting new next_auto_tag_base value');
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = License::class;
|
||||
|
|
File diff suppressed because it is too large
Load diff
18
build/mix.js
18
build/mix.js
|
@ -63,12 +63,19 @@
|
|||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 58);
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 61);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ 58:
|
||||
/***/ 6:
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
eval("// removed by extract-text-webpack-plugin//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3Jlc291cmNlcy9hc3NldHMvbGVzcy9vdmVycmlkZXMubGVzcz9mZjJhIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIHJlbW92ZWQgYnkgZXh0cmFjdC10ZXh0LXdlYnBhY2stcGx1Z2luXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9yZXNvdXJjZXMvYXNzZXRzL2xlc3Mvb3ZlcnJpZGVzLmxlc3Ncbi8vIG1vZHVsZSBpZCA9IDZcbi8vIG1vZHVsZSBjaHVua3MgPSAxIl0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZVJvb3QiOiIifQ==");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 61:
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
__webpack_require__(9);
|
||||
|
@ -77,13 +84,6 @@ __webpack_require__(8);
|
|||
module.exports = __webpack_require__(6);
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 6:
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
eval("// removed by extract-text-webpack-plugin//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3Jlc291cmNlcy9hc3NldHMvbGVzcy9vdmVycmlkZXMubGVzcz9mZjJhIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIHJlbW92ZWQgYnkgZXh0cmFjdC10ZXh0LXdlYnBhY2stcGx1Z2luXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9yZXNvdXJjZXMvYXNzZXRzL2xlc3Mvb3ZlcnJpZGVzLmxlc3Ncbi8vIG1vZHVsZSBpZCA9IDZcbi8vIG1vZHVsZSBjaHVua3MgPSAxIl0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZVJvb3QiOiIifQ==");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 7:
|
||||
|
|
135
build/vue.js
135
build/vue.js
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,7 @@
|
|||
"php": ">=5.6.4",
|
||||
"aacotroneo/laravel-saml2": "^0.8.1",
|
||||
"aws/aws-sdk-php-laravel": "^3.1",
|
||||
"barryvdh/laravel-debugbar": "^2.3",
|
||||
"barryvdh/laravel-debugbar": "^2.4",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/common": "^2.7",
|
||||
"doctrine/dbal": "v2.4.2",
|
||||
|
|
586
composer.lock
generated
586
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -26,13 +26,11 @@ class AddNextAutoincrementToSettings extends Migration
|
|||
});
|
||||
|
||||
\Log::debug('Setting '.$next.' as default auto-increment');
|
||||
|
||||
|
||||
if ($settings = App\Models\Setting::first()) {
|
||||
$settings->next_auto_tag_base = $next;
|
||||
$settings->save();
|
||||
$settings->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddHeaderAndFirstRowToImporterTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('imports', function (Blueprint $table) {
|
||||
// Add a json string representing the header row of the csv, and the first row of the csv.
|
||||
$table->text('header_row')->nullable()->default(null);
|
||||
$table->text('first_row')->nullable()->default(null);
|
||||
$table->text('field_map')->nullable()->default(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('imports', function (Blueprint $table) {
|
||||
//
|
||||
$table->dropColumn('header_row');
|
||||
$table->dropColumn('first_row');
|
||||
$table->dropColumn('field_map');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@
|
|||
"devDependencies": {
|
||||
"axios": "^0.15.3",
|
||||
"babel-preset-latest": "^6.24.1",
|
||||
"bootstrap-sass": "^3.3.7",
|
||||
"cross-env": "^3.2.4",
|
||||
"jquery": "^3.1.1",
|
||||
"laravel-mix": "0.12.1",
|
||||
|
@ -22,6 +21,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"blueimp-file-upload": "^9.18.0",
|
||||
"bootstrap": "^3.3.7",
|
||||
"bootstrap-colorpicker": "^2.5.1",
|
||||
"bootstrap-datepicker": "^1.6.4",
|
||||
"bootstrap-less": "^3.3.8",
|
||||
|
@ -29,9 +29,9 @@
|
|||
"fastclick": "^1.0.6",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"papaparse": "^4.3.3",
|
||||
"select2": "^4.0.3",
|
||||
"tether": "^1.4.0",
|
||||
"vue-resource": "^1.3.3",
|
||||
"vue-strap": "github:wffranco/vue-strap"
|
||||
"vue-resource": "^1.3.3"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/css/dist/all.css
vendored
BIN
public/css/dist/all.css
vendored
Binary file not shown.
BIN
public/js/dist/all.js
vendored
BIN
public/js/dist/all.js
vendored
Binary file not shown.
1
resources/assets/js/bootstrap.js
vendored
1
resources/assets/js/bootstrap.js
vendored
|
@ -17,6 +17,7 @@ require('bootstrap-less');
|
|||
*/
|
||||
|
||||
window.Vue = require('vue');
|
||||
window.eventHub = new Vue();
|
||||
require('vue-resource');
|
||||
|
||||
/**
|
||||
|
|
211
resources/assets/js/components/importer/importer-file.vue
Normal file
211
resources/assets/js/components/importer/importer-file.vue
Normal file
|
@ -0,0 +1,211 @@
|
|||
<style>
|
||||
tr {
|
||||
padding-left:30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<tr v-show="processDetail">
|
||||
<td colspan="3">
|
||||
<h4 class="modal-title">Import File:</h4>
|
||||
<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">
|
||||
<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 class="col-md-12" style="padding-top: 30px;">
|
||||
<table class="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>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button type="button" class="btn btn-default" @click="processDetail = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" @click="postSave">Import</button>
|
||||
<div class="alert alert-success col-md-5 col-md-offset-1" style="text-align:left" v-if="statusText">{{ this.statusText }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['file'],
|
||||
data() {
|
||||
return {
|
||||
activeFile: this.file,
|
||||
processDetail: false,
|
||||
statusText: null,
|
||||
options: {
|
||||
importType: this.file.import_type,
|
||||
update: false,
|
||||
importTypes: [
|
||||
{ id: 'asset', text: 'Assets' },
|
||||
{ id: 'accessory', text: 'Accessories' },
|
||||
{ id: 'consumable', text: 'Consumables' },
|
||||
{ id: 'component', text: 'Components' },
|
||||
{ id: 'license', text: 'Licenses' },
|
||||
{ id: 'user', text: 'Users' }
|
||||
],
|
||||
statusText: null,
|
||||
},
|
||||
columnOptions: {
|
||||
general: [
|
||||
{id: 'category', text: 'Category' },
|
||||
{id: 'company', text: 'Company' },
|
||||
{id: 'checkout_to', text: 'Checked out to' },
|
||||
{id: 'email', text: 'Email' },
|
||||
{id: 'first_name', text: 'First Name' },
|
||||
{id: 'item_name', text: 'Item Name' },
|
||||
{id: 'last_name', text: 'Last Name' },
|
||||
{id: 'location', text: 'Location' },
|
||||
{id: 'maintained', text: 'Maintained' },
|
||||
{id: 'manufacturer', text: 'Manufacturer' },
|
||||
{id: 'notes', text: 'Notes' },
|
||||
{id: 'order_number', text: 'Order Number' },
|
||||
{id: 'purchase_cost', text: 'Purchase Cost' },
|
||||
{id: 'purchase_date', text: 'Purchase Date' },
|
||||
{id: 'quantity', text: 'Quantity' },
|
||||
{id: 'requestable', text: 'Requestable' },
|
||||
{id: 'serial', text: 'Serial Number' },
|
||||
{id: 'supplier', text: 'Supplier' },
|
||||
{id: 'username', text: 'Username' },
|
||||
],
|
||||
assets: [
|
||||
{id: 'asset_tag', text: 'Asset Tag' },
|
||||
{id: 'asset_model', text: 'Model Name' },
|
||||
{id: 'image', text: 'Image Filename' },
|
||||
{id: 'model_number', text: 'Model Number' },
|
||||
{id: 'name', text: 'Full Name' },
|
||||
{id: 'status', text: 'Status' },
|
||||
{id: 'warranty_months', text: 'Warranty Months' },
|
||||
],
|
||||
licenses: [
|
||||
{id: 'expiration_date', text: 'Expiration Date' },
|
||||
{id: 'license_email', text: 'Licensed To Email' },
|
||||
{id: 'license_name', text: 'Licensed To Name' },
|
||||
{id: 'purchase_order', text: 'Purchase Order' },
|
||||
{id: 'reassignable', text: 'Reassignable' },
|
||||
{id: 'seats', text: 'Seats' },
|
||||
],
|
||||
users: [
|
||||
{id: 'employee_num', text: 'Employee Number' },
|
||||
{id: 'jobtitle', text: 'Job Title' },
|
||||
{id: 'phone_number', text: 'Phone Number' },
|
||||
],
|
||||
},
|
||||
columnMappings: this.file.field_map || {},
|
||||
activeColumn: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.eventHub.$on('showDetails', this.toggleExtendedDisplay)
|
||||
this.populateSelect2ActiveItems();
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
switch(this.options.importType) {
|
||||
case 'asset':
|
||||
return this.columnOptions.general.concat(this.columnOptions.assets);
|
||||
case 'license':
|
||||
return this.columnOptions.general.concat(this.columnOptions.licenses);
|
||||
case 'user':
|
||||
return this.columnOptions.general.concat(this.columnOptions.users);
|
||||
}
|
||||
return this.columnOptions.general;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
postSave() {
|
||||
this.statusText = "Processing...";
|
||||
this.$http.post('/api/v1/imports/process/'+this.file.id, {
|
||||
'import-update': this.options.update,
|
||||
'import-type': this.options.importType,
|
||||
'column-mappings': this.columnMappings
|
||||
}).then( (response) => {
|
||||
// Success
|
||||
this.statusText = "Success... Redirecting.";
|
||||
window.location.href = response.body.messages.redirect_url;
|
||||
}, (response) => {
|
||||
// Failure
|
||||
if(response.body.status == 'import-errors') {
|
||||
window.eventHub.$emit('importErrors', response.body.messages);
|
||||
this.statusText = "Error";
|
||||
} else {
|
||||
this.$emit('alert', {
|
||||
message: response.body.messages,
|
||||
type: "danger",
|
||||
visible: true,
|
||||
})
|
||||
}
|
||||
this.displayImportModal=false;
|
||||
});
|
||||
},
|
||||
populateSelect2ActiveItems() {
|
||||
if(this.file.field_map == null) {
|
||||
// Begin by populating the active selection in dropdowns with blank values.
|
||||
for (var i=0; i < this.file.header_row.length; i++) {
|
||||
this.$set(this.columnMappings, this.file.header_row[i], null);
|
||||
}
|
||||
// Then, for any values that have a likely match, we make that active.
|
||||
for(var j=0; j < this.columns.length; j++) {
|
||||
let column = this.columns[j];
|
||||
let index = this.file.header_row.indexOf(column.text)
|
||||
if(index != -1) {
|
||||
this.$set(this.columnMappings, this.file.header_row[index], column.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleExtendedDisplay(fileId) {
|
||||
if(fileId == this.file.id) {
|
||||
this.processDetail = !this.processDetail
|
||||
}
|
||||
},
|
||||
updateModel(header, value) {
|
||||
console.log(header, value);
|
||||
this.columnMappings[header] = value;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
select2: require('../select2.vue')
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -7,95 +7,9 @@ th {
|
|||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="row">
|
||||
<alert v-show="alert.visible" :alertType="alert.type" v-on:hide="alert.visible = false">{{ alert.message }}</alert>
|
||||
<errors :errors="importErrors"></errors>
|
||||
<modal v-model="displayImportModal" effect="fade">
|
||||
<div slot="modal-header" class="modal-header">
|
||||
<h4 class="modal-title">Import File:</h4>
|
||||
</div>
|
||||
<div slot="modal-body" class="modal-body">
|
||||
<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-8 col-xs-12">
|
||||
<select2 :options="modal.importTypes" v-model="modal.importType">
|
||||
<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-8 col-xs-12">
|
||||
<input type="checkbox" name="import-update" v-model="modal.update">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" slot="modal-footer">
|
||||
<div class="alert alert-success col-md-5 col-md-offset-1" style="text-align:left" v-if="modal.statusText">{{ this.modal.statusText }}</div>
|
||||
<button type="button" class="btn btn-default" @click="displayImportModal = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" @click="postSave">Process</button>
|
||||
</div>
|
||||
</modal>
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<!-- The fileinput-button span is used to style the file input field as button -->
|
||||
<span class="btn btn-info fileinput-button">
|
||||
<span>Select Import File...</span>
|
||||
<!-- The file input field used as target for the file upload widget -->
|
||||
<input id="fileupload" type="file" name="files[]" data-url="/api/v1/imports" accept="text/csv">
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-9" v-show="progress.visible" style="padding-bottom:20px">
|
||||
<div class="col-md-11">
|
||||
<div class="progress progress-striped-active" style="margin-top: 8px">
|
||||
<div class="progress-bar" :class="progress.currentClass" role="progressbar" :style="progressWidth">
|
||||
<span>{{ progress.statusText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="padding-top: 30px;">
|
||||
<table class="table table-striped" id="upload-table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Created</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="file in files">
|
||||
<td>{{ file.file_path }}</td>
|
||||
<td>{{ file.created_at }} </td>
|
||||
<td>{{ file.filesize }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" @click="showModal(file)">Process</button>
|
||||
<button class="btn btn-danger" @click="deleteFile(file)"><i class="fa fa-trash icon-white"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
require('blueimp-file-upload');
|
||||
var modal = require('vue-strap').modal
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
|
@ -110,18 +24,6 @@ th {
|
|||
message: null,
|
||||
visible: false,
|
||||
},
|
||||
modal: {
|
||||
importType: 'asset',
|
||||
update: false,
|
||||
importTypes: [
|
||||
{ id: 'asset', text: 'Assets' },
|
||||
{ id: 'accessory', text: 'Accessories' },
|
||||
{ id: 'consumable', text: 'Consumable' },
|
||||
{ id: 'component', text: 'Components' },
|
||||
{ id: 'license', text: 'Licenses' }
|
||||
],
|
||||
statusText: null,
|
||||
},
|
||||
importErrors: null,
|
||||
progress: {
|
||||
currentClass: "progress-bar-warning",
|
||||
|
@ -136,6 +38,7 @@ th {
|
|||
* Prepare the component (Vue 2.x).
|
||||
*/
|
||||
mounted() {
|
||||
window.eventHub.$on('importErrors', this.updateImportErrors);
|
||||
this.fetchFiles();
|
||||
let vm = this;
|
||||
$('#fileupload').fileupload({
|
||||
|
@ -144,6 +47,7 @@ th {
|
|||
vm.progress.currentClass="progress-bar-success";
|
||||
vm.progress.statusText = "Success!";
|
||||
vm.files = data.result.files.concat(vm.files);
|
||||
console.log(data.result.header_row);
|
||||
},
|
||||
add(e, data) {
|
||||
data.headers = {
|
||||
|
@ -179,7 +83,7 @@ th {
|
|||
},
|
||||
deleteFile(file, key) {
|
||||
this.$http.delete("/api/v1/imports/"+file.id)
|
||||
.then((response) => this.files.splice(key, 1), // Success
|
||||
.then((response) => this.files.splice(key, 1), // Success, remove file from array.
|
||||
(response) => {// Fail
|
||||
this.alert.type="danger";
|
||||
this.alert.visible=true;
|
||||
|
@ -187,33 +91,15 @@ th {
|
|||
}
|
||||
);
|
||||
},
|
||||
showModal(file) {
|
||||
this.activeFile = file;
|
||||
this.displayImportModal = true;
|
||||
toggleEvent(fileId) {
|
||||
window.eventHub.$emit('showDetails', fileId)
|
||||
},
|
||||
|
||||
postSave() {
|
||||
this.modal.statusText = "Processing...";
|
||||
this.$http.post('/api/v1/imports/process/'+this.activeFile.id, {
|
||||
'import-update': this.modal.update,
|
||||
'import-type': this.modal.importType
|
||||
}).then( (response) => {
|
||||
// Success
|
||||
this.modal.statusText = "Success... Redirecting.";
|
||||
window.location.href = response.body.messages.redirect_url;
|
||||
}, (response) => {
|
||||
// Failure
|
||||
if(response.body.status == 'import-errors') {
|
||||
this.importErrors = response.body.messages;
|
||||
} else {
|
||||
this.alert.message= response.body.messages;
|
||||
this.alert.type="danger";
|
||||
this.alert.visible=true;
|
||||
}
|
||||
this.displayImportModal=false;
|
||||
});
|
||||
updateAlert(alert) {
|
||||
this.alert = alert;
|
||||
},
|
||||
updateImportErrors(errors) {
|
||||
this.importErrors = errors;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -223,10 +109,9 @@ th {
|
|||
},
|
||||
|
||||
components: {
|
||||
modal,
|
||||
errors: require('./importer-errors.vue'),
|
||||
alert: require('../alert.vue'),
|
||||
select2: require('../select2.vue')
|
||||
errors: require('./importer-errors.vue'),
|
||||
importFile: require('./importer-file.vue'),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
|
||||
<style scoped>
|
||||
.select2-dropdown {
|
||||
z-index:9999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
|
@ -22,14 +25,18 @@
|
|||
.select2({
|
||||
data: this.options
|
||||
})
|
||||
.on('change', function() { vm.$emit('input', this.value) } );
|
||||
.on('change', function() { vm.$emit('input', this.value) } )
|
||||
.val(this.value).trigger('change');
|
||||
},
|
||||
watch: {
|
||||
value: function (value) {
|
||||
$(this.$el).val(value)
|
||||
},
|
||||
options: function (options) {
|
||||
$(this.$el).select2({data: options})
|
||||
var vm = this;
|
||||
$(this.$el).select2('destroy').empty().select2({data: options})
|
||||
.on('change', function() { vm.$emit('input', this.value) } )
|
||||
.val(this.value).trigger('change');
|
||||
},
|
||||
destroyed: function() {
|
||||
$(this.$el).off().select2('destroy')
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
@icon-font-path: '../fonts';
|
||||
|
||||
@import '../../../node_modules/bootstrap/less/bootstrap';
|
||||
@import '../../../node_modules/bootstrap-less/bootstrap/bootstrap';
|
||||
@import '../../../node_modules/ekko-lightbox/ekko-lightbox';
|
||||
@import '../../../node_modules/bootstrap-colorpicker/src/less/colorpicker';
|
||||
|
||||
|
|
|
@ -518,22 +518,3 @@ Form::macro('customfield_elements', function ($name = "customfield_elements", $s
|
|||
return $select;
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Form::macro('header_list', function ($headers = null, $name = "header_list", $selected = null, $class = null) {
|
||||
|
||||
|
||||
$select = '<select name="'.$name.'" class="'.$class.'" style="width: 100%">';
|
||||
$select .= '<option value="">Do Not Import</option>';
|
||||
|
||||
foreach ($headers as $header => $label) {
|
||||
$select .= '<option value="'.str_slug($label).'"'.($selected == str_slug($label) ? ' selected="selected"' : '').'>'.e($label).'</option> '."\n";
|
||||
}
|
||||
|
||||
$select .= '</select>';
|
||||
|
||||
return $select;
|
||||
|
||||
});
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
@extends('layouts/default')
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/lib/jquery.fileupload.css') }}">
|
||||
{{-- Page title --}}
|
||||
@section('title')
|
||||
{{ trans('general.import') }}
|
||||
@parent
|
||||
@stop
|
||||
|
||||
{{-- Page content --}}
|
||||
@section('content')
|
||||
<div id="app">
|
||||
<importer>
|
||||
</div>
|
||||
@stop
|
||||
|
||||
@section('moar_scripts')
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,233 +0,0 @@
|
|||
@extends('layouts.default')
|
||||
|
||||
{{-- Page title --}}
|
||||
@section('title')
|
||||
Map Import Fields
|
||||
@parent
|
||||
@stop
|
||||
|
||||
@section('header_right')
|
||||
<a href="{{ URL::previous() }}" class="btn btn-primary pull-right">
|
||||
{{ trans('general.back') }}</a>
|
||||
@stop
|
||||
|
||||
|
||||
|
||||
{{-- Page content --}}
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
|
||||
</h3>
|
||||
@if (isset($helpText))
|
||||
<div class="box-tools pull-right">
|
||||
<button class="slideout-menu-toggle btn btn-box-tool btn-box-tool-lg" data-toggle="tooltip" title="Help"><i class="fa fa-question"></i></button>
|
||||
</div>
|
||||
@endif
|
||||
</div><!-- /.box-header -->
|
||||
|
||||
<div class="box-body">
|
||||
<form id="create-form" class="form-horizontal" method="post" action="{{ (isset($formAction)) ? $formAction : \Request::url() }}" autocomplete="off" role="form" enctype="multipart/form-data">
|
||||
|
||||
<!-- CSRF Token -->
|
||||
{{ csrf_field() }}
|
||||
|
||||
<pre>
|
||||
@php
|
||||
print_r($first_row);
|
||||
@endphp
|
||||
</pre>
|
||||
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h3>Standard Fields</h3>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Check out to User (First Last)
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'user_name_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Username
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'username_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Email
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'email_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Item Name
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'item_name_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Asset Tag
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'asset_tag_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Serial Number
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'serial_number_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Model name
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'model_name_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Model Number
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'model_number_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Category
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'category_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Manufacturer
|
||||
</label>
|
||||
<div class="col-md-4 required">
|
||||
{!! Form::header_list($header_rows, 'manufacturer_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Company
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'company_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Location
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'location_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Purchase Date
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'purchase_date_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Purchase Cost
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'purchase_cost_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Status
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'status_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Notes
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'notes_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">Image Filename
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'image_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($custom_fields->count() > 0)
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h3>Custom Fields</h3>
|
||||
<hr>
|
||||
</div>
|
||||
@foreach ($custom_fields as $custom_field)
|
||||
<div class="form-group">
|
||||
<label for="url" class="col-md-3 control-label">{{ $custom_field->name }}
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{!! Form::header_list($header_rows, 'image_header', Input::old('header_row'), 'select2') !!}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
<div class="box-footer text-right">
|
||||
<a class="btn btn-link" href="{{ URL::previous() }}">{{ trans('button.cancel') }}</a>
|
||||
<button type="submit" class="btn btn-success"><i class="fa fa-check icon-white"></i> {{ trans('general.process') }}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ((isset($helpText)) && (isset($helpTitle)))
|
||||
<div class="slideout-menu">
|
||||
<a href="#" class="slideout-menu-toggle pull-right">×</a>
|
||||
<h3>
|
||||
{{ $helpTitle}}
|
||||
</h3>
|
||||
<p>{{ $helpText }} </p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@stop
|
||||
|
86
resources/views/importer/import.blade.php
Normal file
86
resources/views/importer/import.blade.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
@extends('layouts/default')
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/lib/jquery.fileupload.css') }}">
|
||||
|
||||
{{-- Hide importer until vue has rendered it, if we continue using vue for other things we should move this higher in the style --}}
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
{{-- Page title --}}
|
||||
@section('title')
|
||||
{{ trans('general.import') }}
|
||||
@parent
|
||||
@stop
|
||||
|
||||
{{-- Page content --}}
|
||||
@section('content')
|
||||
<div id="app">
|
||||
<importer inline-template v-cloak>
|
||||
<div class="row">
|
||||
<alert v-show="alert.visible" :alert-type="alert.type" v-on:hide="alert.visible = false">@{{ alert.message }}</alert>
|
||||
<errors :errors="importErrors"></errors>
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<!-- The fileinput-button span is used to style the file input field as button -->
|
||||
<span class="btn btn-info fileinput-button">
|
||||
<span>Select Import File...</span>
|
||||
<!-- The file input field used as target for the file upload widget -->
|
||||
<input id="fileupload" type="file" name="files[]" data-url="/api/v1/imports" accept="text/csv">
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-9" v-show="progress.visible" style="padding-bottom:20px">
|
||||
<div class="col-md-11">
|
||||
<div class="progress progress-striped-active" style="margin-top: 8px">
|
||||
<div class="progress-bar" :class="progress.currentClass" role="progressbar" :style="progressWidth">
|
||||
<span>@{{ progress.statusText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="padding-top: 30px;">
|
||||
<table class="table table-striped" id="upload-table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Created</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="currentFile in files">
|
||||
<tr>
|
||||
<td>@{{ currentFile.file_path }}</td>
|
||||
<td>@{{ currentFile.created_at }} </td>
|
||||
<td>@{{ currentFile.filesize }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" @click="toggleEvent(currentFile.id)">Process</button>
|
||||
<button class="btn btn-danger" @click="deleteFile(currentFile)"><i class="fa fa-trash icon-white"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<import-file :key="currentFile.id" :file="currentFile" @alert="updateAlert(alert)">
|
||||
</import-file>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</importer>
|
||||
</div>
|
||||
@stop
|
||||
|
||||
@section('moar_scripts')
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -473,8 +473,8 @@
|
|||
</li>
|
||||
@endcan
|
||||
@can('create', \App\Models\Asset::class)
|
||||
<li{!! (Request::is('hardware/import*') ? ' class="active"' : '') !!}>
|
||||
<a href="{{ url('hardware/import') }}">
|
||||
<li{!! (Request::is('import/*') ? ' class="active"' : '') !!}>
|
||||
<a href="{{ route('imports.index') }}">
|
||||
<i class="fa fa-cloud-download"></i>
|
||||
<span>@lang('general.import')</span>
|
||||
</a>
|
||||
|
|
|
@ -171,7 +171,20 @@ Route::resource('groups', 'GroupsController', [
|
|||
'parameters' => ['group' => 'group_id']
|
||||
]);
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Importer Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
|
|
||||
|
|
||||
*/
|
||||
Route::group([ 'prefix' => 'import', 'middleware' => ['auth']], function () {
|
||||
Route::get('/', [
|
||||
'as' => 'imports.index',
|
||||
'uses' => 'ImportsController@index'
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
@ -79,23 +79,6 @@ Route::group(
|
|||
'uses' => 'AssetsController@displayFile'
|
||||
]);
|
||||
|
||||
Route::post( 'import/process/', [ 'as' => 'assets/import/process-file',
|
||||
'uses' => 'AssetsController@postProcessImportFile'
|
||||
]);
|
||||
|
||||
Route::get( 'import/delete/{filename}', [ 'as' => 'assets/import/delete-file',
|
||||
'uses' => 'AssetsController@getDeleteImportFile'
|
||||
]);
|
||||
|
||||
Route::get('import/map',[
|
||||
'as' => 'import.map',
|
||||
'uses' => 'AssetsController@getImportMap'
|
||||
]);
|
||||
|
||||
Route::get('import',[
|
||||
'as' => 'assets/import',
|
||||
'uses' => 'AssetsController@getImportUpload'
|
||||
]);
|
||||
|
||||
Route::post(
|
||||
'bulkedit',
|
||||
|
|
1001
sample_csvs/MOCK_USERS.csv
Normal file
1001
sample_csvs/MOCK_USERS.csv
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue