3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 03:06:23 +00:00

Filled in some missing searchable relations for locations, companies, maintenances, and depts

This commit is contained in:
snipe
2026-07-21 21:46:31 +01:00
parent 3f5504008b
commit 4b40a7864e
10 changed files with 203 additions and 0 deletions

View File

@ -88,6 +88,7 @@ class Actionlog extends SnipeModel
*/
protected $searchableRelations = [
'company' => ['name'],
'location' => ['name'],
'adminuser' => ['first_name', 'last_name', 'username', 'email', 'employee_num'],
'user' => ['first_name', 'last_name', 'username', 'email', 'employee_num'],
'assets' => ['asset_tag', 'name', 'serial', 'order_number', 'notes', 'purchase_date'],

View File

@ -98,6 +98,7 @@ final class Company extends SnipeModel
* @var array
*/
protected $searchableRelations = [
'parent' => ['name'],
'adminuser' => ['first_name', 'last_name', 'display_name'],
];

View File

@ -92,6 +92,8 @@ class Department extends SnipeModel
protected $searchableRelations = [
'adminuser' => ['first_name', 'last_name', 'display_name'],
'company' => ['name'],
'location' => ['name'],
'manager' => ['first_name', 'last_name', 'display_name'],
];
public function isDeletable()

View File

@ -115,6 +115,7 @@ class Location extends SnipeModel
protected $searchableRelations = [
'parent' => ['name'],
'company' => ['name'],
'manager' => ['first_name', 'last_name', 'display_name'],
'adminuser' => ['first_name', 'last_name', 'display_name'],
];

View File

@ -107,6 +107,9 @@ class Maintenance extends SnipeModel implements ICompanyableChild
'asset.model' => ['name', 'model_number'],
'asset.supplier' => ['name'],
'asset.status' => ['name'],
'asset.company' => ['name'],
'asset.location' => ['name'],
'asset.defaultLoc' => ['name'],
'supplier' => ['name'],
'adminuser' => ['first_name', 'last_name', 'display_name'],
'maintenanceType' => ['name'],

View File

@ -40,6 +40,29 @@ class IndexCompaniesTest extends TestCase
}
public function test_search_matches_parent_company_name()
{
// Companies table shows parent as a column. Before adding parent
// to Company's $searchableRelations the search silently returned
// nothing when typing a parent company's name.
$actor = User::factory()->superuser()->create();
$parent = Company::factory()->create(['name' => 'Umbrella Holdings LLC']);
$child = Company::factory()->create(['parent_id' => $parent->id]);
$standalone = Company::factory()->create();
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.companies.index', ['search' => 'Umbrella Holdings']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($child->id, $ids, 'Child should match on parent-company name');
$this->assertContains($parent->id, $ids, 'Parent should still match on its own name');
$this->assertNotContains($standalone->id, $ids);
}
public function test_adheres_to_full_multiple_companies_support_scoping()
{

View File

@ -89,6 +89,47 @@ class DepartmentsIndexTest extends TestCase
->assertResponseContainsInRows($departmentB);
}
public function test_search_matches_location_name()
{
// Departments table shows location as a column; before adding
// location to Department's $searchableRelations the search
// silently returned nothing when typing a location's name.
$actor = User::factory()->superuser()->create();
$location = Location::factory()->create(['name' => 'Bratislava HQ']);
$matchingDepartment = Department::factory()->create(['location_id' => $location->id]);
$otherDepartment = Department::factory()->create();
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.departments.index', ['search' => 'Bratislava']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingDepartment->id, $ids);
$this->assertNotContains($otherDepartment->id, $ids);
}
public function test_search_matches_manager_name()
{
$actor = User::factory()->superuser()->create();
$manager = User::factory()->create(['first_name' => 'Ekaterina', 'last_name' => 'Volkova']);
$matchingDepartment = Department::factory()->create(['manager_id' => $manager->id]);
$otherDepartment = Department::factory()->create();
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.departments.index', ['search' => 'Ekaterina']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingDepartment->id, $ids);
$this->assertNotContains($otherDepartment->id, $ids);
}
public function test_department_index_filters_all_supported_exact_fields()
{
$user = User::factory()->superuser()->create();

View File

@ -40,4 +40,26 @@ class IndexLocationsTest extends TestCase
])
->assertJson(fn (AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function test_search_matches_manager_name()
{
// Locations table shows manager as a column; before adding manager
// to Location's $searchableRelations the search silently returned
// nothing when typing a manager's name.
$actor = User::factory()->superuser()->create();
$manager = User::factory()->create(['first_name' => 'Anastasia', 'last_name' => 'Krupin']);
$matchingLocation = Location::factory()->create(['manager_id' => $manager->id]);
$otherLocation = Location::factory()->create();
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.locations.index', ['search' => 'Anastasia']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingLocation->id, $ids);
$this->assertNotContains($otherLocation->id, $ids);
}
}

View File

@ -164,4 +164,79 @@ class IndexMaintenanceTest extends TestCase
->getJson(route('api.maintenances.index', ['sort' => 'completed_at', 'order' => 'desc']))
->assertOk();
}
public function test_search_matches_asset_company_name()
{
// The maintenances table shows the asset's company, so searching
// for a company name from the search box should return matching
// rows. Before this fix, asset.company wasn't in the Maintenance
// model's $searchableRelations, so the search silently matched
// nothing.
$actor = User::factory()->superuser()->create();
$company = \App\Models\Company::factory()->create(['name' => 'Acme Widgets Ltd']);
$matchingAsset = Asset::factory()->create(['company_id' => $company->id]);
$matchingMaintenance = Maintenance::factory()->create(['asset_id' => $matchingAsset->id]);
$otherAsset = Asset::factory()->create();
$otherMaintenance = Maintenance::factory()->create(['asset_id' => $otherAsset->id]);
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.maintenances.index', ['search' => 'Acme Widgets']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingMaintenance->id, $ids);
$this->assertNotContains($otherMaintenance->id, $ids);
}
public function test_search_matches_asset_location_name()
{
$actor = User::factory()->superuser()->create();
$location = \App\Models\Location::factory()->create(['name' => 'Zanzibar HQ']);
$matchingAsset = Asset::factory()->create(['location_id' => $location->id]);
$matchingMaintenance = Maintenance::factory()->create(['asset_id' => $matchingAsset->id]);
$otherAsset = Asset::factory()->create();
$otherMaintenance = Maintenance::factory()->create(['asset_id' => $otherAsset->id]);
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.maintenances.index', ['search' => 'Zanzibar']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingMaintenance->id, $ids);
$this->assertNotContains($otherMaintenance->id, $ids);
}
public function test_search_matches_asset_default_location_name()
{
// defaultLoc is the RTD (return-to) location on the asset. When an
// asset is unassigned, the maintenance table shows the RTD
// location as its location column; searching that value should
// still find the row.
$actor = User::factory()->superuser()->create();
$rtd = \App\Models\Location::factory()->create(['name' => 'Reykjavik Warehouse']);
$matchingAsset = Asset::factory()->create(['rtd_location_id' => $rtd->id, 'location_id' => null]);
$matchingMaintenance = Maintenance::factory()->create(['asset_id' => $matchingAsset->id]);
$otherAsset = Asset::factory()->create();
$otherMaintenance = Maintenance::factory()->create(['asset_id' => $otherAsset->id]);
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.maintenances.index', ['search' => 'Reykjavik']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingMaintenance->id, $ids);
$this->assertNotContains($otherMaintenance->id, $ids);
}
}

View File

@ -188,4 +188,38 @@ class ActivityReportTest extends TestCase
->assertJson(fn (AssertableJson $json) => $json->has('rows', 7)->etc());
}
public function test_search_matches_action_log_location_name()
{
// Activity Report eager-loads and shows the location on each
// action_log row (e.g. checkouts to a location). Before adding
// location to Actionlog's $searchableRelations, typing that
// location's name into the report search silently returned
// nothing.
$actor = User::factory()->superuser()->create();
$location = \App\Models\Location::factory()->create(['name' => 'Kraków Office']);
$matchingLog = Actionlog::factory()->create([
'action_type' => 'checkout',
'item_type' => Asset::class,
'item_id' => Asset::factory()->create()->id,
'location_id' => $location->id,
]);
$otherLog = Actionlog::factory()->create([
'action_type' => 'checkout',
'item_type' => Asset::class,
'item_id' => Asset::factory()->create()->id,
'location_id' => null,
]);
$ids = collect($this->actingAsForApi($actor)
->getJson(route('api.activity.index', ['search' => 'Kraków']))
->assertOk()
->json('rows'))
->pluck('id')
->all();
$this->assertContains($matchingLog->id, $ids);
$this->assertNotContains($otherLog->id, $ids);
}
}