mirror of
https://github.com/snipe/snipe-it.git
synced 2026-05-05 22:25:34 +00:00
@ -1329,6 +1329,8 @@ class Helper
|
||||
return 'fa-solid fa-store';
|
||||
case 'manufacturer':
|
||||
return 'fa-solid fa-building';
|
||||
case 'category':
|
||||
return 'fa-solid fa-table-columns';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -240,6 +240,9 @@ class ImportController extends Controller
|
||||
case 'manufacturer':
|
||||
$redirectTo = 'manufacturers.index';
|
||||
break;
|
||||
case 'category':
|
||||
$redirectTo = 'categories.index';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($errors) { //Failure
|
||||
|
||||
99
app/Importer/CategoryImporter.php
Normal file
99
app/Importer/CategoryImporter.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Importer;
|
||||
|
||||
use App\Models\Category;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* When we are importing users via an Asset/etc import, we use createOrFetchUser() in
|
||||
* Importer\Importer.php. [ALG]
|
||||
*
|
||||
* Class CategoryImporter
|
||||
*/
|
||||
class CategoryImporter extends ItemImporter
|
||||
{
|
||||
protected $categories;
|
||||
|
||||
public function __construct($filename)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
}
|
||||
|
||||
protected function handle($row)
|
||||
{
|
||||
parent::handle($row);
|
||||
$this->createCategoryIfNotExists($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a supplier if a duplicate does not exist.
|
||||
* @todo Investigate how this should interact with Importer::createCategoryIfNotExists
|
||||
*
|
||||
* @author A. Gianotto
|
||||
* @since 6.1.0
|
||||
* @param array $row
|
||||
*/
|
||||
public function createCategoryIfNotExists(array $row)
|
||||
{
|
||||
|
||||
$editingCategory = false;
|
||||
|
||||
$supplier = Category::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
||||
|
||||
if ($this->findCsvMatch($row, 'id')!='') {
|
||||
// Override supplier if an ID was given
|
||||
\Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id'));
|
||||
$supplier = Category::find($this->findCsvMatch($row, 'id'));
|
||||
}
|
||||
|
||||
|
||||
if ($supplier) {
|
||||
if (! $this->updating) {
|
||||
$this->log('A matching Category '.$this->item['name'].' already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log('Updating Category');
|
||||
$editingCategory = true;
|
||||
} else {
|
||||
$this->log('No Matching Category, Create a new one');
|
||||
$supplier = new Category;
|
||||
$supplier->created_by = auth()->id();
|
||||
}
|
||||
|
||||
// Pull the records from the CSV to determine their values
|
||||
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
|
||||
$this->item['notes'] = trim($this->findCsvMatch($row, 'notes'));
|
||||
$this->item['eula_text'] = trim($this->findCsvMatch($row, 'eula_text'));
|
||||
$this->item['category_type'] = trim($this->findCsvMatch($row, 'category_type'));
|
||||
$this->item['use_default_eula'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'use_default_eula'))) == 1) ? 1 : 0;
|
||||
$this->item['require_acceptance'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_acceptance'))) == 1) ? 1 : 0;
|
||||
$this->item['checkin_email'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'checkin_email'))) == 1) ? 1 : 0;
|
||||
|
||||
|
||||
Log::debug('Item array is: ');
|
||||
Log::debug(print_r($this->item, true));
|
||||
|
||||
|
||||
if ($editingCategory) {
|
||||
Log::debug('Updating existing supplier');
|
||||
$supplier->update($this->sanitizeItemForUpdating($supplier));
|
||||
} else {
|
||||
Log::debug('Creating supplier');
|
||||
$supplier->fill($this->sanitizeItemForStoring($supplier));
|
||||
}
|
||||
|
||||
if ($supplier->save()) {
|
||||
$this->log('Category '.$supplier->name.' created or updated from CSV import');
|
||||
return $supplier;
|
||||
|
||||
} else {
|
||||
Log::debug($supplier->getErrors());
|
||||
$this->logError($supplier, 'Category "'.$this->item['name'].'"');
|
||||
return $supplier->errors;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ class ItemImporter extends Importer
|
||||
protected function determineCheckout($row)
|
||||
{
|
||||
// Locations don't get checked out to anyone/anything
|
||||
if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class) || (get_class($this) == SupplierImporter::class) || (get_class($this) == ManufacturerImporter::class)) {
|
||||
if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class) || (get_class($this) == SupplierImporter::class) || (get_class($this) == ManufacturerImporter::class) || (get_class($this) == CategoryImporter::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ class Importer extends Component
|
||||
public $consumables_fields;
|
||||
public $components_fields;
|
||||
public $manufacturers_fields;
|
||||
public $categories_fields;
|
||||
public $aliases_fields;
|
||||
|
||||
protected $rules = [
|
||||
@ -103,6 +104,9 @@ class Importer extends Component
|
||||
case 'manufacturer':
|
||||
$results = $this->manufacturers_fields;
|
||||
break;
|
||||
case 'category':
|
||||
$results = $this->categories_fields;
|
||||
break;
|
||||
default:
|
||||
$results = [];
|
||||
}
|
||||
@ -176,6 +180,7 @@ class Importer extends Component
|
||||
'user' => trans('general.users'),
|
||||
'supplier' => trans('general.suppliers'),
|
||||
'manufacturer' => trans('general.manufacturers'),
|
||||
'category' => trans('general.categories'),
|
||||
];
|
||||
|
||||
/**
|
||||
@ -379,6 +384,18 @@ class Importer extends Component
|
||||
'url' => trans('general.url'),
|
||||
];
|
||||
|
||||
$this->categories_fields = [
|
||||
'id' => trans('general.id'),
|
||||
'name' => trans('general.name'),
|
||||
'notes' => trans('general.notes'),
|
||||
'category_type' => trans('general.type'),
|
||||
'eula_text' => trans('admin/categories/general.eula_text'),
|
||||
// 'use_default_eula' => trans('admin/categories/general.use_default_eula'),
|
||||
// 'require_acceptance' => trans('admin/categories/general.require_acceptance'),
|
||||
// 'checkin_email' => trans('admin/categories/general.checkin_email'),
|
||||
];
|
||||
|
||||
|
||||
|
||||
$this->assetmodels_fields = [
|
||||
'category' => trans('general.category'),
|
||||
|
||||
@ -219,4 +219,18 @@ class ImportFactory extends Factory
|
||||
});
|
||||
}
|
||||
|
||||
public function categories()
|
||||
{
|
||||
return $this->state(function (array $attributes) {
|
||||
$fileBuilder = Importing\CategoriesImportFileBuilder::new();
|
||||
$attributes['name'] = "Category {$attributes['name']}";
|
||||
$attributes['import_type'] = 'category';
|
||||
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||
|
||||
return $attributes;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@
|
||||
|
||||
|
||||
|
||||
@if (($typeOfImport != 'location' && $typeOfImport!= 'assetModel' && $typeOfImport!= 'component' && $typeOfImport!= 'supplier') && $typeOfImport!= 'manufacturer' && ($typeOfImport!=''))
|
||||
@if (($typeOfImport != 'location' && $typeOfImport!= 'assetModel' && $typeOfImport!= 'component' && $typeOfImport!= 'supplier') && $typeOfImport!= 'manufacturer' && $typeOfImport!= 'category' && ($typeOfImport!=''))
|
||||
<label class="form-control">
|
||||
<input type="checkbox" name="send_welcome" data-livewire-component="{{ $this->getId() }}" wire:model.live="send_welcome">
|
||||
{{ trans('general.send_welcome_email_to_users') }}
|
||||
|
||||
16
sample_csvs/categories-sample.csv
Normal file
16
sample_csvs/categories-sample.csv
Normal file
@ -0,0 +1,16 @@
|
||||
name,category_type,notes,require_acceptance,checkin_email,use_default_eula,eula_text
|
||||
Liquid Aminios Acid - Braggs,accessory,Duis bibendum.,0,1,1,"Mauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci. Mauris lacinia sapien quis libero. Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum."
|
||||
Tea - Camomele,consumable,In quis justo. Maecenas rhoncus aliquam lacus.,0,1,1,Ut tellus. Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi. Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit.
|
||||
Honey - Lavender,consumable,Etiam vel augue. Vestibulum rutrum rutrum neque.,1,1,0,"Curabitur convallis. Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus. Mauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis."
|
||||
Apple - Royal Gala,asset,Curabitur at ipsum ac tellus semper interdum. Mauris ullamcorper purus sit amet nulla.,0,1,1,"Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh. Quisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros. Vestibulum ac est lacinia nisi venenatis tristique. Fusce congue, diam id ornare imperdiet, sapien urna pretium nisl, ut volutpat sapien arcu sed augue. Aliquam erat volutpat. In congue."
|
||||
Isomalt,component,"Quisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla.",1,0,1,"In quis justo. Maecenas rhoncus aliquam lacus. Morbi quis tortor id nulla ultrices aliquet. Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui. Maecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc."
|
||||
Muffin Mix - Oatmeal,component,"In est risus, auctor sed, tristique in, tempus sit amet, sem.",1,1,1,Nulla facilisi. Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque. Quisque porta volutpat erat.
|
||||
"Seedlings - Buckwheat, Organic",asset,"In hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante.",1,1,0,"Duis bibendum. Morbi non quam nec dui luctus rutrum. Nulla tellus. In sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus. Suspendisse potenti."
|
||||
Vinegar - Rice,license,Duis mattis egestas metus.,1,1,1,"Nam dui. Proin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius. Integer ac leo. Pellentesque ultrices mattis odio."
|
||||
Banana - Green,asset,Curabitur convallis.,1,1,1,"Morbi a ipsum. Integer a nibh. In quis justo. Maecenas rhoncus aliquam lacus. Morbi quis tortor id nulla ultrices aliquet. Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui."
|
||||
Wine - Zinfandel California 2002,license,Duis aliquam convallis nunc. Proin at turpis a pede posuere nonummy.,0,1,1,Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi. Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque. Quisque porta volutpat erat.
|
||||
Tuna - Salad Premix,asset,"Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.",1,1,1,"Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui. Maecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti. Nullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris. Morbi non lectus."
|
||||
Cheese - Goat With Herbs,consumable,Nulla ut erat id mauris vulputate elementum. Nullam varius.,1,1,1,Aliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros. Suspendisse accumsan tortor quis turpis. Sed ante. Vivamus tortor. Duis mattis egestas metus.
|
||||
Veal - Insides Provini,asset,"Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.",1,1,1,"Proin risus. Praesent lectus. Vestibulum quam sapien, varius ut, blandit non, interdum in, ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio. Curabitur convallis. Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus."
|
||||
Island Oasis - Peach Daiquiri,component,"Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh. Quisque id justo sit amet sapien dignissim vestibulum.",1,1,1,"Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio. Curabitur convallis. Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus. Mauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci. Mauris lacinia sapien quis libero. Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum."
|
||||
Beer - Camerons Cream Ale,accessory,Vivamus vestibulum sagittis sapien.,1,1,1,"Duis aliquam convallis nunc. Proin at turpis a pede posuere nonummy. Integer non velit. Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque. Duis bibendum."
|
||||
|
135
tests/Feature/Importing/Api/ImportCategoriesTest.php
Normal file
135
tests/Feature/Importing/Api/ImportCategoriesTest.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Importing\Api;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\User;
|
||||
use App\Models\Import;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\Concerns\TestsPermissionsRequirement;
|
||||
use Tests\Support\Importing\CleansUpImportFiles;
|
||||
use Tests\Support\Importing\CategoriesImportFileBuilder as ImportFileBuilder;
|
||||
|
||||
class ImportCategoriesTest extends ImportDataTestCase implements TestsPermissionsRequirement
|
||||
{
|
||||
use CleansUpImportFiles;
|
||||
use WithFaker;
|
||||
|
||||
protected function importFileResponse(array $parameters = []): TestResponse
|
||||
{
|
||||
if (!array_key_exists('import-type', $parameters)) {
|
||||
$parameters['import-type'] = 'category';
|
||||
}
|
||||
|
||||
return parent::importFileResponse($parameters);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testRequiresPermission()
|
||||
{
|
||||
$this->actingAsForApi(User::factory()->create());
|
||||
|
||||
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function importCategory(): void
|
||||
{
|
||||
$importFileBuilder = ImportFileBuilder::new();
|
||||
$row = $importFileBuilder->firstRow();
|
||||
$import = Import::factory()->categories()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||
|
||||
$this->actingAsForApi(User::factory()->superuser()->create());
|
||||
$this->importFileResponse(['import' => $import->id, 'send-welcome' => 0])
|
||||
->assertOk()
|
||||
->assertExactJson([
|
||||
'payload' => null,
|
||||
'status' => 'success',
|
||||
'messages' => ['redirect_url' => route('categories.index')]
|
||||
]);
|
||||
|
||||
$newCategory = Category::query()
|
||||
->where('name', $row['name'])
|
||||
->sole();
|
||||
|
||||
$this->assertEquals($row['name'], $newCategory->name);
|
||||
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
|
||||
{
|
||||
$row = ImportFileBuilder::new()->definition();
|
||||
$row['unknownColumnInCsvFile'] = 'foo';
|
||||
|
||||
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||
|
||||
$this->actingAsForApi(User::factory()->superuser()->create());
|
||||
|
||||
$import = Import::factory()->categories()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||
|
||||
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||
}
|
||||
|
||||
|
||||
#[Test]
|
||||
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||
{
|
||||
$importFileBuilder = ImportFileBuilder::new(['name' => '']);
|
||||
$import = Import::factory()->categories()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||
|
||||
$this->actingAsForApi(User::factory()->superuser()->create());
|
||||
|
||||
$this->importFileResponse(['import' => $import->id])
|
||||
->assertInternalServerError()
|
||||
->assertExactJson([
|
||||
'status' => 'import-errors',
|
||||
'payload' => null,
|
||||
'messages' => [
|
||||
'' => [
|
||||
'Category ""' => [
|
||||
'name' =>
|
||||
['The name field is required.'],
|
||||
],
|
||||
]
|
||||
|
||||
]
|
||||
]);
|
||||
|
||||
$newCategory = Category::query()
|
||||
->where('name', $importFileBuilder->firstRow()['name'])
|
||||
->get();
|
||||
|
||||
$this->assertCount(0, $newCategory);
|
||||
}
|
||||
|
||||
|
||||
#[Test]
|
||||
public function updateCategoryFromImport(): void
|
||||
{
|
||||
$category = Category::factory()->create()->refresh();
|
||||
$importFileBuilder = ImportFileBuilder::new(['name' => $category->name, 'category_type' => 'asset', 'notes' => $category->notes, 'use_default_eula' => 0, 'require_acceptance' => 0, 'checkin_email' => 0]);
|
||||
|
||||
$row = $importFileBuilder->firstRow();
|
||||
$import = Import::factory()->categories()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||
|
||||
$this->actingAsForApi(User::factory()->superuser()->create());
|
||||
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||
|
||||
$updatedCategory = Category::query()->find($category->id);
|
||||
$updatedAttributes = [
|
||||
'name',
|
||||
];
|
||||
|
||||
$this->assertEquals($row['name'], $updatedCategory->name);
|
||||
|
||||
$this->assertEquals(
|
||||
Arr::except($category->attributesToArray(), array_merge($updatedAttributes, $category->getDates())),
|
||||
Arr::except($updatedCategory->attributesToArray(), array_merge($updatedAttributes, $category->getDates())),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
56
tests/Support/Importing/CategoriesImportFileBuilder.php
Normal file
56
tests/Support/Importing/CategoriesImportFileBuilder.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Support\Importing;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Build a users import file at runtime for testing.
|
||||
*
|
||||
* @template Row of array{
|
||||
* companyName?: string,
|
||||
* email?: string,
|
||||
* employeeNumber?: int,
|
||||
* firstName?: string,
|
||||
* lastName?: string,
|
||||
* location?: string,
|
||||
* phoneNumber?: string,
|
||||
* position?: string,
|
||||
* username?: string,
|
||||
* }
|
||||
*
|
||||
* @extends FileBuilder<Row>
|
||||
*/
|
||||
class CategoriesImportFileBuilder extends FileBuilder
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function getDictionary(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'name',
|
||||
'notes' => 'notes',
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$faker = fake();
|
||||
|
||||
return [
|
||||
'name' => $faker->company,
|
||||
'notes' => $faker->sentence,
|
||||
'category_type' => 'asset',
|
||||
'require_acceptance' => 1,
|
||||
'use_default_eula' => 1,
|
||||
'checkin_email' => 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user