Add ability to tie locations to companies

Locations are the last big part of the application that can't be tied to companies.
This can be a problem with FullMultipleCompanySupport, because you can't restrict the visibility of locations to the company of the users.

In order to change this, add a company_id to the locations table and wire everything up in the views and controllers.
Aditionally add a new formatter to filter the locations to a specific company, like it is done for assets.

Locations are properly scoped to the users company if FullMultipleCompanySupport is enabled.
If a parent location of a location has a different company than the user, the location does not show up.
This commit is contained in:
Tobias Regnery
2022-02-10 11:36:36 +01:00
parent 95dba6c426
commit 1ccbf8942c
12 changed files with 147 additions and 12 deletions
@@ -9,6 +9,7 @@ use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@@ -48,6 +49,7 @@ class LocationsController extends Controller
'rtd_assets_count',
'currency',
'ldap_ou',
'company_id',
];
$locations = Location::with('parent', 'manager', 'children')->select([
@@ -68,12 +70,15 @@ class LocationsController extends Controller
'locations.image',
'locations.ldap_ou',
'locations.currency',
'locations.company_id',
])->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count');
$locations = Company::scopeCompanyables($locations);
if ($request->filled('search')) {
$locations = $locations->TextSearch($request->input('search'));
}
@@ -106,6 +111,10 @@ class LocationsController extends Controller
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
}
if ($request->filled('company_id')) {
$locations->where('locations.company_id', '=', $request->input('company_id'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -122,6 +131,9 @@ class LocationsController extends Controller
case 'manager':
$locations->OrderManager($order);
break;
case 'company':
$locations->OrderCompany($order);
break;
default:
$locations->orderBy($sort, $order);
break;
@@ -147,6 +159,7 @@ class LocationsController extends Controller
$this->authorize('create', Location::class);
$location = new Location;
$location->fill($request->all());
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
$location = $request->handleImages($location);
if ($location->save()) {
@@ -166,7 +179,7 @@ class LocationsController extends Controller
public function show($id) : JsonResponse | array
{
$this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children')
$location = Location::with('parent', 'manager', 'children', 'company')
->select([
'locations.id',
'locations.name',
@@ -209,6 +222,10 @@ class LocationsController extends Controller
$location->fill($request->all());
$location = $request->handleImages($location);
if ($request->filled('company_id')) {
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
}
if ($location->isValid()) {
$location->save();
@@ -305,6 +322,8 @@ class LocationsController extends Controller
'locations.image',
]);
$locations = Company::scopeCompanyables($locations);
$page = 1;
if ($request->filled('page')) {
$page = $request->input('page');
+19 -8
View File
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use App\Models\User;
use Illuminate\Support\Facades\Storage;
@@ -78,6 +79,7 @@ class LocationsController extends Controller
$location->created_by = auth()->id();
$location->phone = request('phone');
$location->fax = request('fax');
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$location = $request->handleImages($location);
@@ -138,6 +140,7 @@ class LocationsController extends Controller
$location->fax = request('fax');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$location = $request->handleImages($location);
@@ -211,20 +214,22 @@ class LocationsController extends Controller
public function print_assigned($id) : View | RedirectResponse
{
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -296,10 +301,16 @@ class LocationsController extends Controller
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -58,6 +58,10 @@ class LocationsTransformer
'name'=> e($location->parent->name),
] : null,
'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null,
'company' => ($location->company) ? [
'id' => (int) $location->company->id,
'name'=> e($location->company->name)
] : null,
'children' => $children_arr,
];
+30 -1
View File
@@ -22,6 +22,7 @@ class Location extends SnipeModel
protected $presenter = \App\Presenters\LocationPresenter::class;
use Presentable;
use SoftDeletes;
use CompanyableTrait;
protected $table = 'locations';
protected $rules = [
@@ -34,11 +35,13 @@ class Location extends SnipeModel
'zip' => 'max:10|nullable',
'manager_id' => 'exists:users,id|nullable',
'parent_id' => 'nullable|exists:locations,id|non_circular:locations,id',
'company_id' => 'integer|nullable|exists:companies,id',
];
protected $casts = [
'parent_id' => 'integer',
'manager_id' => 'integer',
'company_id' => 'integer',
];
/**
@@ -72,6 +75,7 @@ class Location extends SnipeModel
'currency',
'manager_id',
'image',
'company_id',
];
protected $hidden = ['user_id'];
@@ -90,7 +94,8 @@ class Location extends SnipeModel
* @var array
*/
protected $searchableRelations = [
'parent' => ['name'],
'parent' => ['name'],
'company' => ['name']
];
@@ -214,6 +219,17 @@ class Location extends SnipeModel
->with('parent');
}
/**
* Establishes the locations -> company relationship
*
* @author [T. Regnery] [<tobias.regnery@gmail.com>]
* @since [v7.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function company()
{
return $this->belongsTo(\App\Models\Company::class, 'company_id');
}
/**
* Find the manager of a location
@@ -313,4 +329,17 @@ class Location extends SnipeModel
{
return $query->leftJoin('users as location_user', 'locations.manager_id', '=', 'location_user.id')->orderBy('location_user.first_name', $order)->orderBy('location_user.last_name', $order);
}
/**
* Query builder scope to order on company
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderCompany($query, $order)
{
return $query->leftJoin('companies as company_sort', 'locations.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order);
}
}
+9
View File
@@ -27,6 +27,15 @@ class LocationPresenter extends Presenter
'title' => trans('general.id'),
'visible' => false,
],
[
'field' => 'company',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.company'),
'visible' => false,
'formatter' => 'locationCompanyObjFilterFormatter'
],
[
'field' => 'name',
'searchable' => true,
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddCompanyIdToLocations extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('locations', function (Blueprint $table) {
$table->integer('company_id')->unsigned()->nullable();
$table->index(['company_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('locations', function (Blueprint $table) {
$table->dropIndex(['company_id']);
$table->dropColumn('company_id');
});
}
}
+3
View File
@@ -17,6 +17,9 @@
<!-- Manager-->
@include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/users/table.manager'), 'fieldname' => 'manager_id'])
<!-- Company -->
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
@include ('partials.forms.edit.phone')
@include ('partials.forms.edit.fax')
+1 -1
View File
@@ -41,7 +41,7 @@
data-sort-order="asc"
id="locationTable"
class="table table-striped snipe-table"
data-url="{{ route('api.locations.index') }}"
data-url="{{ route('api.locations.index', array('company_id'=>e(Request::get('company_id')))) }}"
data-export-options='{
"fileName": "export-locations-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
+5 -1
View File
@@ -53,7 +53,11 @@
@if ($parent)
{{ $parent->present()->fullName() }}
@endif
<br>
@if ($company)
<b>{{ trans('admin/companies/table.name') }}:</b> {{ $company->present()->Name() }}</b>
<br>
@endif
@if ($manager)
<b>{{ trans('general.manager') }}</b> {{ $manager->present()->fullName() }}<br>
@endif
+3
View File
@@ -412,6 +412,9 @@
@if ($location->manager)
<li>{{ trans('admin/users/table.manager') }}: {!! $location->manager->present()->nameUrl() !!}</li>
@endif
@if ($location->company)
<li>{{ trans('admin/companies/table.name') }}: {!! $location->company->present()->nameUrl() !!}</li>
@endif
@if ($location->parent)
<li>{{ trans('admin/locations/table.parent') }}: {!! $location->parent->present()->nameUrl() !!}</li>
@endif
+10
View File
@@ -11,6 +11,16 @@
</div>
@include('modals.partials.name', ['item' => new \App\Models\Location(), 'required' => 'true'])
<!-- Setup of default company, taken from asset creator -->
@if ($user->company)
<input type="hidden" name="company_id" id='modal-company' value='{{ $user->company->id }}' class="form-control">
@endif
<!-- Select company, only for users with multicompany access - replace default company -->
<div class="dynamic-form-row">
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
</div>
<div class="dynamic-form-row">
<div class="col-md-4 col-xs-12"><label for="modal-city">{{ trans('general.city') }}:</label></div>
<div class="col-md-8 col-xs-12"><input type='text' name="city" id='modal-city' class="form-control"></div>
@@ -748,6 +748,14 @@
}
}
function locationCompanyObjFilterFormatter(value, row) {
if (value) {
return '<a href="{{ url('/') }}/locations/?company_id=' + row.company.id + '">' + row.company.name + '</a>';
} else {
return value;
}
}
function employeeNumFormatter(value, row) {
if ((row) && (row.assigned_to) && ((row.assigned_to.employee_number))) {