Merge branch 'develop' of github.com:snipe/snipe-it into develop

# Conflicts:
#	composer.lock
This commit is contained in:
snipe 2017-06-23 18:55:18 -07:00
commit 9e0c5e50b6
48 changed files with 2009 additions and 1556 deletions

View file

@ -22,9 +22,9 @@ before_script:
- php artisan migrate --env=testing-ci --database=mysql --force
- ./vendor/bin/codecept build
- php artisan --env=testing-ci key:generate
- php artisan --env=testing-ci snipeit:travisci-install
- php artisan --env=testing-ci db:seed --database=mysql --force
- php artisan --env=testing-ci snipeit:create-admin --first_name=Alison --last_name=Foobar --email=me@example.com --username=snipe --password=password
- php artisan --env=testing-ci snipeit:travisci-install
- php artisan --env=testing-ci passport:install
- php artisan serve --env=testing-ci --port=8000 --host=localhost &
- sleep 5

View file

@ -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),
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View 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);
}
}

View file

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

View file

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

View file

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

View file

@ -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'].'"');
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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'].'"');
}
}
}

View 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;
}
}

View 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 | | |

View file

@ -6,4 +6,9 @@ use Illuminate\Database\Eloquent\Model;
class Import extends Model
{
protected $casts = [
'header_row' => 'array',
'first_row' => 'array',
'field_map' => 'json'
];
}

View file

@ -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',

View file

@ -22,10 +22,8 @@ class AccessoryObserver
$logAction->item_type = Accessory::class;
$logAction->item_id = $accessory->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('update');
}
@ -41,13 +39,12 @@ 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;
$logAction->item_id = $accessory->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('create');
}
@ -64,7 +61,7 @@ class AccessoryObserver
$logAction->item_type = Accessory::class;
$logAction->item_id = $accessory->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
}

View file

@ -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::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('update');
}
@ -41,13 +38,12 @@ 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;
$logAction->item_id = $asset->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('create');
}
@ -64,7 +60,7 @@ class AssetObserver
$logAction->item_type = Asset::class;
$logAction->item_id = $asset->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
}

View file

@ -22,10 +22,8 @@ class ComponentObserver
$logAction->item_type = Component::class;
$logAction->item_id = $component->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('update');
}
@ -41,13 +39,12 @@ 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;
$logAction->item_id = $component->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('create');
}
@ -64,7 +61,7 @@ class ComponentObserver
$logAction->item_type = Component::class;
$logAction->item_id = $component->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
}

View file

@ -22,10 +22,8 @@ class ConsumableObserver
$logAction->item_type = Consumable::class;
$logAction->item_id = $consumable->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('update');
}
@ -41,13 +39,12 @@ 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;
$logAction->item_id = $consumable->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('create');
}
@ -64,7 +61,7 @@ class ConsumableObserver
$logAction->item_type = Consumable::class;
$logAction->item_id = $consumable->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
}

View file

@ -22,10 +22,8 @@ class LicenseObserver
$logAction->item_type = License::class;
$logAction->item_id = $license->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('update');
}
@ -41,13 +39,12 @@ 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;
$logAction->item_id = $license->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('create');
}
@ -64,7 +61,7 @@ class LicenseObserver
$logAction->item_type = License::class;
$logAction->item_id = $license->id;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::user()->id;
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
}

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because one or more lines are too long

View file

@ -7,7 +7,7 @@
"require": {
"php": ">=5.6.4",
"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",

View file

@ -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();
}
}

View file

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

View file

@ -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"
}
}

Binary file not shown.

BIN
public/js/dist/all.js vendored

Binary file not shown.

View file

@ -17,6 +17,7 @@ require('bootstrap-less');
*/
window.Vue = require('vue');
window.eventHub = new Vue();
require('vue-resource');
/**

View 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>

View file

@ -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'),
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff