diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index 5cfa8973ca..dc19cf076f 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -82,6 +82,7 @@ class ItemImporter extends Importer $this->item['qty'] = $this->findCsvMatch($row, 'quantity'); $this->item['requestable'] = $this->findCsvMatch($row, 'requestable'); $this->item['created_by'] = auth()->id(); + $this->item['asset_tag'] = $this->findCsvMatch($row, 'asset_tag'); $this->item['serial'] = $this->findCsvMatch($row, 'serial'); $this->item['item_no'] = trim($this->findCsvMatch($row, 'item_no')); diff --git a/tests/Feature/Importing/Api/ImportAssetsTest.php b/tests/Feature/Importing/Api/ImportAssetsTest.php index 3f88f33e3c..54e95293e2 100644 --- a/tests/Feature/Importing/Api/ImportAssetsTest.php +++ b/tests/Feature/Importing/Api/ImportAssetsTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Importing\Api; use App\Models\Actionlog as ActionLog; use App\Models\Asset; +use App\Models\Company; use App\Models\CustomField; use App\Models\Import; use App\Models\User; @@ -641,4 +642,88 @@ class ImportAssetsTest extends ImportDataTestCase implements TestsPermissionsReq $this->assertNotEquals($encryptedMacAddress, $macAddress); } + + #[Test] + public function import_asset_checkout_is_blocked_when_fmcs_companies_differ(): void + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + $user = User::factory()->for($companyB)->create(); + $this->settings->enableMultipleFullCompanySupport(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $companyA->name, + 'assigneeUsername' => $user->username, + ]); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertNull($newAsset->assigned_to, 'Asset should not be checked out when item and user companies differ under FMCS'); + } + + #[Test] + public function import_asset_checkout_is_allowed_when_fmcs_companies_match(): void + { + $company = Company::factory()->create(); + $user = User::factory()->for($company)->create(); + $this->settings->enableMultipleFullCompanySupport(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'assigneeUsername' => $user->username, + ]); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertEquals($user->id, $newAsset->assigned_to, 'Asset should be checked out when companies match under FMCS'); + } + + #[Test] + public function import_asset_checkout_is_blocked_when_floater_disabled_and_user_has_no_company(): void + { + $company = Company::factory()->create(); + $user = User::factory()->create(['company_id' => null]); + $this->settings->enableMultipleFullCompanySupport()->disableFloaterMode(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'assigneeUsername' => $user->username, + ]); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertNull($newAsset->assigned_to, 'Asset should not be checked out to a no-company user when floater mode is off'); + } + + #[Test] + public function import_asset_checkout_is_allowed_when_floater_enabled_and_user_has_no_company(): void + { + $company = Company::factory()->create(); + $user = User::factory()->create(['company_id' => null]); + $this->settings->enableFloaterMode(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'assigneeUsername' => $user->username, + ]); + + $import = Import::factory()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newAsset = Asset::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertEquals($user->id, $newAsset->assigned_to, 'Asset should be checked out to a no-company user when floater mode is on'); + } } diff --git a/tests/Feature/Importing/Api/ImportComponentsTest.php b/tests/Feature/Importing/Api/ImportComponentsTest.php index 9364ab3ca8..8c481f55a9 100644 --- a/tests/Feature/Importing/Api/ImportComponentsTest.php +++ b/tests/Feature/Importing/Api/ImportComponentsTest.php @@ -3,6 +3,8 @@ namespace Tests\Feature\Importing\Api; use App\Models\Actionlog as ActionLog; +use App\Models\Asset; +use App\Models\Company; use App\Models\Component; use App\Models\Import; use App\Models\User; @@ -348,4 +350,88 @@ class ImportComponentsTest extends ImportDataTestCase implements TestsPermission $this->assertNull($newComponent->image); $this->assertNull($newComponent->notes); } + + #[Test] + public function import_component_checkout_to_asset_is_blocked_when_fmcs_companies_differ(): void + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + $asset = Asset::factory()->for($companyB)->create(); + $this->settings->enableMultipleFullCompanySupport(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $companyA->name, + 'assetTag' => $asset->asset_tag, + ]); + + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponent = Component::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertEquals(0, $newComponent->assets()->count(), 'Component should not be checked out when item and asset companies differ under FMCS'); + } + + #[Test] + public function import_component_checkout_to_asset_is_allowed_when_fmcs_companies_match(): void + { + $company = Company::factory()->create(); + $asset = Asset::factory()->for($company)->create(); + $this->settings->enableMultipleFullCompanySupport(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'assetTag' => $asset->asset_tag, + ]); + + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponent = Component::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertEquals(1, $newComponent->assets()->count(), 'Component should be checked out when companies match under FMCS'); + } + + #[Test] + public function import_component_checkout_to_asset_is_blocked_when_floater_disabled_and_asset_has_no_company(): void + { + $company = Company::factory()->create(); + $asset = Asset::factory()->create(['company_id' => null]); + $this->settings->enableMultipleFullCompanySupport()->disableFloaterMode(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'assetTag' => $asset->asset_tag, + ]); + + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponent = Component::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertEquals(0, $newComponent->assets()->count(), 'Component should not be checked out to a no-company asset when floater mode is off'); + } + + #[Test] + public function import_component_checkout_to_asset_is_allowed_when_floater_enabled_and_asset_has_no_company(): void + { + $company = Company::factory()->create(); + $asset = Asset::factory()->create(['company_id' => null]); + $this->settings->enableFloaterMode(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'assetTag' => $asset->asset_tag, + ]); + + $import = Import::factory()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $newComponent = Component::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $this->assertEquals(1, $newComponent->assets()->count(), 'Component should be checked out to a no-company asset when floater mode is on'); + } } diff --git a/tests/Feature/Importing/Api/ImportLicenseTest.php b/tests/Feature/Importing/Api/ImportLicenseTest.php index 8699689660..8dac1de093 100644 --- a/tests/Feature/Importing/Api/ImportLicenseTest.php +++ b/tests/Feature/Importing/Api/ImportLicenseTest.php @@ -3,8 +3,10 @@ namespace Tests\Feature\Importing\Api; use App\Models\Actionlog as ActivityLog; +use App\Models\Company; use App\Models\Import; use App\Models\License; +use App\Models\LicenseSeat; use App\Models\User; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Support\Str; @@ -399,4 +401,96 @@ class ImportLicenseTest extends ImportDataTestCase implements TestsPermissionsRe $this->assertNull($newLicense->deprecate); $this->assertNull($newLicense->min_amt); } + + #[Test] + public function import_license_checkout_is_blocked_when_fmcs_companies_differ(): void + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + $user = User::factory()->for($companyB)->create(); + $this->settings->enableMultipleFullCompanySupport(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $companyA->name, + 'checkoutUsername' => $user->username, + 'seats' => 5, + ]); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $license = License::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $checkedOutSeat = LicenseSeat::where('license_id', $license->id)->whereNotNull('assigned_to')->first(); + $this->assertNull($checkedOutSeat, 'License seat should not be checked out when item and user companies differ under FMCS'); + } + + #[Test] + public function import_license_checkout_is_allowed_when_fmcs_companies_match(): void + { + $company = Company::factory()->create(); + $user = User::factory()->for($company)->create(); + $this->settings->enableMultipleFullCompanySupport(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'checkoutUsername' => $user->username, + 'seats' => 5, + ]); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $license = License::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $checkedOutSeat = LicenseSeat::where('license_id', $license->id)->where('assigned_to', $user->id)->first(); + $this->assertNotNull($checkedOutSeat, 'License seat should be checked out when companies match under FMCS'); + } + + #[Test] + public function import_license_checkout_is_blocked_when_floater_disabled_and_user_has_no_company(): void + { + $company = Company::factory()->create(); + $user = User::factory()->create(['company_id' => null]); + $this->settings->enableMultipleFullCompanySupport()->disableFloaterMode(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'checkoutUsername' => $user->username, + 'seats' => 5, + ]); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $license = License::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $checkedOutSeat = LicenseSeat::where('license_id', $license->id)->whereNotNull('assigned_to')->first(); + $this->assertNull($checkedOutSeat, 'License seat should not be checked out to a no-company user when floater mode is off'); + } + + #[Test] + public function import_license_checkout_is_allowed_when_floater_enabled_and_user_has_no_company(): void + { + $company = Company::factory()->create(); + $user = User::factory()->create(['company_id' => null]); + $this->settings->enableFloaterMode(); + + $importFileBuilder = ImportFileBuilder::new([ + 'companyName' => $company->name, + 'checkoutUsername' => $user->username, + 'seats' => 5, + ]); + + $import = Import::factory()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]); + + $this->actingAsForApi(User::factory()->superuser()->create()); + $this->importFileResponse(['import' => $import->id])->assertOk(); + + $license = License::where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole(); + $checkedOutSeat = LicenseSeat::where('license_id', $license->id)->where('assigned_to', $user->id)->first(); + $this->assertNotNull($checkedOutSeat, 'License seat should be checked out to a no-company user when floater mode is on'); + } } diff --git a/tests/Support/Importing/ComponentsImportFileBuilder.php b/tests/Support/Importing/ComponentsImportFileBuilder.php index afc4c0e8a5..68ad054162 100644 --- a/tests/Support/Importing/ComponentsImportFileBuilder.php +++ b/tests/Support/Importing/ComponentsImportFileBuilder.php @@ -31,6 +31,7 @@ class ComponentsImportFileBuilder extends FileBuilder protected function getDictionary(): array { return [ + 'assetTag' => 'Asset Tag', 'category' => 'Category', 'companyName' => 'Company', 'itemName' => 'item Name', @@ -51,6 +52,7 @@ class ComponentsImportFileBuilder extends FileBuilder $faker = fake(); return [ + 'assetTag' => '', 'category' => Str::random(), 'companyName' => Str::random()." {$faker->companySuffix}", 'itemName' => Str::random(), diff --git a/tests/Support/Importing/LicensesImportFileBuilder.php b/tests/Support/Importing/LicensesImportFileBuilder.php index e7c6ca3ee0..33cd09a38e 100644 --- a/tests/Support/Importing/LicensesImportFileBuilder.php +++ b/tests/Support/Importing/LicensesImportFileBuilder.php @@ -39,6 +39,9 @@ class LicensesImportFileBuilder extends FileBuilder { return [ 'category' => 'Category', + 'checkoutEmail' => 'Email', + 'checkoutFullName' => 'Full Name', + 'checkoutUsername' => 'Username', 'companyName' => 'Company', 'expirationDate' => 'expiration date', 'isMaintained' => 'maintained', @@ -66,6 +69,9 @@ class LicensesImportFileBuilder extends FileBuilder return [ 'category' => Str::random(), + 'checkoutEmail' => '', + 'checkoutFullName' => '', + 'checkoutUsername' => '', 'companyName' => Str::random()." {$faker->companySuffix}", 'expirationDate' => $faker->date, 'isMaintained' => $faker->randomElement(['TRUE', 'FALSE']),