diff --git a/app/Console/Commands/TestLocationsFMCS.php b/app/Console/Commands/TestLocationsFMCS.php new file mode 100644 index 000000000000..fb606e6db43a --- /dev/null +++ b/app/Console/Commands/TestLocationsFMCS.php @@ -0,0 +1,43 @@ +info('Test for inconsistencies if FullMultipleCompanySupport with scoped locations will be used'); + $this->info('Depending on the database size this will take a while, output will be displayed after the complete test is over'); + + // if parameter location_id is set, only test this location + $location_id = null; + if ($this->option('location_id')) { + $location_id = $this->option('location_id'); + } + $ret = Helper::test_locations_fmcs(true, $location_id); + + foreach($ret as $output) { + $this->info($output); + } + } +} diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 95a344dce9e7..14b8fcba5fe2 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -12,6 +12,7 @@ use App\Models\Setting; use App\Models\Statuslabel; use App\Models\License; +use App\Models\Location; use Illuminate\Support\Facades\Crypt; use Illuminate\Contracts\Encryption\DecryptException; use Carbon\Carbon; @@ -1529,4 +1530,68 @@ static public function getRedirectOption($request, $id, $table, $item_id = null) } return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error')); } + + /** + * Check for inconsistencies before activating scoped locations with FullMultipleCompanySupport + * If there are locations with different companies than related objects unforseen problems could arise + * + * @author T. Regnery + * @since 7.0 + * + * @param $artisan when false, bail out on first inconsistent entry + * @param $location_id when set, only test this specific location + * @param $new_company_id in case of updating a location, this is the newly requested company_id + * @return string [] + */ + static public function test_locations_fmcs($artisan, $location_id = null, $new_company_id = null) { + $ret = []; + + if ($location_id) { + $location = Location::find($location_id); + if ($location) { + $locations = collect([])->push(Location::find($location_id)); + } + } else { + $locations = Location::all(); + } + + foreach($locations as $location) { + // in case of an update of a single location use the newly requested company_id + if ($new_company_id) { + $location_company = $new_company_id; + } else { + $location_company = $location->company_id; + } + + // depending on the relationship we must use different operations to retrieve the objects + $keywords_relation = ['many' => ['users', 'assets', 'rtd_assets', 'consumables', 'components', 'accessories', 'assignedAssets', 'assignedAccessories'], + 'one' => ['parent', 'manager']]; + + // In case of a single location the children must be checked either, becuase we don't walk every location + if ($location_id) { + $keywords_relation['many'][] = 'children'; + } + + foreach ($keywords_relation as $relation => $keywords) { + foreach($keywords as $keyword) { + if ($relation == 'many') { + $items = $location->$keyword->all(); + } else { + $items = collect([])->push($location->$keyword); + } + + foreach ($items as $item) { + if ($item && $item->company_id != $location_company) { + $ret[] = 'type: ' . get_class($item) . ', id: ' . $item->id . ', company_id: ' . $item->company_id . ', location company_id: ' . $location_company; + // when not called from artisan command we bail out on the first error + if (!$artisan) { + return $ret; + } + } + } + } + } + } + return $ret; + } } diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index f4f788d56393..be96dc4c98f8 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -12,7 +12,9 @@ use App\Models\Accessory; use App\Models\AccessoryCheckout; use App\Models\Asset; +use App\Models\Company; use App\Models\Location; +use App\Models\Setting; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; @@ -46,6 +48,7 @@ public function index(Request $request) : JsonResponse | array 'id', 'image', 'ldap_ou', + 'company_id', 'manager_id', 'name', 'rtd_assets_count', @@ -73,7 +76,9 @@ public function index(Request $request) : JsonResponse | array 'locations.image', 'locations.ldap_ou', 'locations.currency', + 'locations.company_id', ]) + ->withCount('assignedAssets as assigned_assets_count') ->withCount('assignedAssets as assigned_assets_count') ->withCount('assets as assets_count') ->withCount('assignedAccessories as assigned_accessories_count') @@ -82,6 +87,11 @@ public function index(Request $request) : JsonResponse | array ->withCount('children as children_count') ->withCount('users as users_count'); + // Only scope locations if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $locations = Company::scopeCompanyables($locations); + } + if ($request->filled('search')) { $locations = $locations->TextSearch($request->input('search')); } @@ -114,6 +124,10 @@ public function index(Request $request) : JsonResponse | array $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'); @@ -130,6 +144,9 @@ public function index(Request $request) : JsonResponse | array case 'manager': $locations->OrderManager($order); break; + case 'company': + $locations->OrderCompany($order); + break; default: $locations->orderBy($sort, $order); break; @@ -157,6 +174,15 @@ public function store(ImageUploadRequest $request) : JsonResponse $location->fill($request->all()); $location = $request->handleImages($location); + // Only scope location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->get('company_id')); + // check if parent is set and has a different company + if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) { + response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent')); + } + } + if ($location->save()) { return response()->json(Helper::formatStandardApiResponse('success', (new LocationsTransformer)->transformLocation($location), trans('admin/locations/message.create.success'))); } @@ -174,7 +200,7 @@ public function store(ImageUploadRequest $request) : JsonResponse 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', @@ -217,6 +243,19 @@ public function update(ImageUploadRequest $request, $id) : JsonResponse $location->fill($request->all()); $location = $request->handleImages($location); + if ($request->filled('company_id')) { + // Only scope location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->get('company_id')); + // check if there are related objects with different company + if (Helper::test_locations_fmcs(false, $id, $location->company_id)) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations')); + } + } else { + $location->company_id = $request->get('company_id'); + } + } + if ($location->isValid()) { $location->save(); @@ -337,6 +376,11 @@ public function selectlist(Request $request) : array 'locations.image', ]); + // Only scope locations if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $locations = Company::scopeCompanyables($locations); + } + $page = 1; if ($request->filled('page')) { $page = $request->input('page'); diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index 75abce97ed5b..c7a8476c4628 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -2,10 +2,13 @@ namespace App\Http\Controllers; +use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; use App\Models\Actionlog; use App\Models\Asset; +use App\Models\Company; use App\Models\Location; +use App\Models\Setting; use App\Models\User; use Illuminate\Support\Facades\Storage; use Illuminate\Http\Request; @@ -78,6 +81,18 @@ public function store(ImageUploadRequest $request) : RedirectResponse $location->created_by = auth()->id(); $location->phone = request('phone'); $location->fax = request('fax'); + $location->company_id = Company::getIdForCurrentUser($request->input('company_id')); + + // Only scope the location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->input('company_id')); + // check if parent is set and has a different company + if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) { + return redirect()->back()->withInput()->withInput()->with('error', 'different company than parent'); + } + } else { + $location->company_id = $request->input('company_id'); + } $location = $request->handleImages($location); @@ -139,6 +154,17 @@ public function update(ImageUploadRequest $request, $locationId = null) : Redire $location->ldap_ou = $request->input('ldap_ou'); $location->manager_id = $request->input('manager_id'); + // Only scope the location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->input('company_id')); + // check if there are related objects with different company + if (Helper::test_locations_fmcs(false, $locationId, $location->company_id)) { + return redirect()->back()->withInput()->withInput()->with('error', 'error scoped locations'); + } + } else { + $location->company_id = $request->input('company_id'); + } + $location = $request->handleImages($location); if ($location->save()) { @@ -211,20 +237,22 @@ public function show($id = null) : View | RedirectResponse 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 +324,16 @@ public function print_all_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('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')); } diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 1bc120d1c15d..6f69de8bdab0 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -314,7 +314,23 @@ public function postSettings(Request $request) : RedirectResponse $setting->modellist_displays = implode(',', $request->input('show_in_model_list')); } + $old_locations_fmcs = $setting->scope_locations_fmcs; $setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0'); + $setting->scope_locations_fmcs = $request->input('scope_locations_fmcs', '0'); + + // Backward compatibility for locations makes no sense without FullMultipleCompanySupport + if (!$setting->full_multiple_companies_support) { + $setting->scope_locations_fmcs = '0'; + } + + // check for inconsistencies when activating scoped locations + if ($old_locations_fmcs == '0' && $setting->scope_locations_fmcs == '1') { + $ret = Helper::test_locations_fmcs(false); + if (count($ret) != 0) { + return redirect()->back()->withInput()->with('error', 'Inconsistencies with scoped locations found, please use php artisan snipeit:test-locations-fmcs for details'); + } + } + $setting->unique_serial = $request->input('unique_serial', '0'); $setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0'); $setting->show_images_in_email = $request->input('show_images_in_email', '0'); diff --git a/app/Http/Transformers/LocationsTransformer.php b/app/Http/Transformers/LocationsTransformer.php index d6ba2f01b20d..2347141fdc94 100644 --- a/app/Http/Transformers/LocationsTransformer.php +++ b/app/Http/Transformers/LocationsTransformer.php @@ -62,6 +62,10 @@ public function transformLocation(Location $location = null) '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, ]; diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index fc1bb36ab40d..039f8692f6b4 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -61,6 +61,7 @@ class Accessory extends SnipeModel 'qty' => 'required|integer|min:1', 'category_id' => 'required|integer|exists:categories,id', 'company_id' => 'integer|nullable', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|nullable', 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', 'purchase_date' => 'date_format:Y-m-d|nullable', diff --git a/app/Models/Asset.php b/app/Models/Asset.php index ce8b870eb2e0..2fe5b299dff6 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -108,8 +108,8 @@ public function declinedCheckout(User $declinedBy, $signature) 'expected_checkin' => ['nullable', 'date'], 'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'], 'next_audit_date' => ['nullable', 'date'], - 'location_id' => ['nullable', 'exists:locations,id'], - 'rtd_location_id' => ['nullable', 'exists:locations,id'], + 'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], + 'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], 'serial' => ['nullable', 'unique_undeleted:assets,serial'], 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'], @@ -122,7 +122,7 @@ public function declinedCheckout(User $declinedBy, $signature) 'assigned_to' => ['nullable', 'integer'], 'requestable' => ['nullable', 'boolean'], 'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'], - 'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'], + 'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'], 'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL'] ]; diff --git a/app/Models/CompanyableTrait.php b/app/Models/CompanyableTrait.php index 04a620d8e3d5..c4b9b89fbff6 100644 --- a/app/Models/CompanyableTrait.php +++ b/app/Models/CompanyableTrait.php @@ -13,6 +13,13 @@ trait CompanyableTrait */ public static function bootCompanyableTrait() { - static::addGlobalScope(new CompanyableScope); + // In Version 7.0 and before locations weren't scoped by companies, so add a check for the backward compatibility setting + if (__CLASS__ != 'App\Models\Location') { + static::addGlobalScope(new CompanyableScope); + } else { + if (Setting::getSettings()->scope_locations_fmcs == 1) { + static::addGlobalScope(new CompanyableScope); + } + } } } diff --git a/app/Models/Component.php b/app/Models/Component.php index fb77bf082412..7c5aa15d1ad8 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -35,6 +35,7 @@ class Component extends SnipeModel 'category_id' => 'required|integer|exists:categories,id', 'supplier_id' => 'nullable|integer|exists:suppliers,id', 'company_id' => 'integer|nullable|exists:companies,id', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable', 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 30161e84296a..45dd67e1c711 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -49,6 +49,7 @@ class Consumable extends SnipeModel 'qty' => 'required|integer|min:0|max:99999', 'category_id' => 'required|integer', 'company_id' => 'integer|nullable', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|max:99999|nullable', 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', 'purchase_date' => 'date_format:Y-m-d|nullable', diff --git a/app/Models/Location.php b/app/Models/Location.php index f146d37c669b..0418792e3a55 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -4,6 +4,7 @@ use App\Http\Traits\UniqueUndeletedTrait; use App\Models\Asset; +use App\Models\Setting; use App\Models\SnipeModel; use App\Models\Traits\Searchable; use App\Models\User; @@ -18,6 +19,7 @@ class Location extends SnipeModel { use HasFactory; + use CompanyableTrait; protected $presenter = \App\Presenters\LocationPresenter::class; use Presentable; @@ -34,11 +36,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 +76,7 @@ class Location extends SnipeModel 'currency', 'manager_id', 'image', + 'company_id', ]; protected $hidden = ['user_id']; @@ -90,7 +95,8 @@ class Location extends SnipeModel * @var array */ protected $searchableRelations = [ - 'parent' => ['name'], + 'parent' => ['name'], + 'company' => ['name'] ]; @@ -214,6 +220,17 @@ public function parent() ->with('parent'); } + /** + * Establishes the locations -> company relationship + * + * @author [T. Regnery] [] + * @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 @@ -325,4 +342,17 @@ public function scopeOrderManager($query, $order) { 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); + } } diff --git a/app/Models/User.php b/app/Models/User.php index e48b8bf074f8..029bda04613d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -94,7 +94,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'locale' => 'max:10|nullable', 'website' => 'url|nullable|max:191', 'manager_id' => 'nullable|exists:users,id|cant_manage_self', - 'location_id' => 'exists:locations,id|nullable', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'start_date' => 'nullable|date_format:Y-m-d', 'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date', 'autoassign_licenses' => 'boolean', diff --git a/app/Presenters/LocationPresenter.php b/app/Presenters/LocationPresenter.php index af910bfdc141..a7728210a41a 100644 --- a/app/Presenters/LocationPresenter.php +++ b/app/Presenters/LocationPresenter.php @@ -25,7 +25,17 @@ public static function dataTableLayout() 'switchable' => true, '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, 'sortable' => true, diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php index 76ba1b629aa9..a08a035fbbf0 100644 --- a/app/Providers/ValidationServiceProvider.php +++ b/app/Providers/ValidationServiceProvider.php @@ -4,6 +4,7 @@ use App\Models\CustomField; use App\Models\Department; +use App\Models\Location; use App\Models\Setting; use Illuminate\Support\Facades\DB; use Illuminate\Support\ServiceProvider; @@ -353,6 +354,20 @@ public function boot() return in_array($value, $options); }); + + // Validates that the company of the validated object matches the company of the location in case of scoped locations + Validator::extend('fmcs_location', function ($attribute, $value, $parameters, $validator){ + $settings = Setting::getSettings(); + if ($settings->full_multiple_companies_support == '1' && $settings->scope_locations_fmcs == '1') { + $company_id = array_get($validator->getData(), 'company_id'); + $location = Location::find($value); + + if ($company_id != $location->company_id) { + return false; + } + } + return true; + }); } /** diff --git a/database/migrations/2022_02_10_110210_add_company_id_to_locations.php b/database/migrations/2022_02_10_110210_add_company_id_to_locations.php new file mode 100644 index 000000000000..8ef5822812fd --- /dev/null +++ b/database/migrations/2022_02_10_110210_add_company_id_to_locations.php @@ -0,0 +1,35 @@ +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'); + }); + } +} + diff --git a/database/migrations/2023_02_27_092130_add_scope_locations_setting.php b/database/migrations/2023_02_27_092130_add_scope_locations_setting.php new file mode 100644 index 000000000000..c1e2ff83e9fe --- /dev/null +++ b/database/migrations/2023_02_27_092130_add_scope_locations_setting.php @@ -0,0 +1,32 @@ +boolean('scope_locations_fmcs')->default('0')->after('full_multiple_companies_support'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('scope_locations_fmcs'); + }); + } +} \ No newline at end of file diff --git a/resources/lang/de-DE/admin/settings/general.php b/resources/lang/de-DE/admin/settings/general.php index 74b21041f4f9..4f87b81c1be2 100644 --- a/resources/lang/de-DE/admin/settings/general.php +++ b/resources/lang/de-DE/admin/settings/general.php @@ -148,6 +148,8 @@ 'logo_print_assets_help' => 'Firmenlogo anzeigen beim Drucken der Asset-Liste ', 'full_multiple_companies_support_help_text' => 'Beschränkung von Benutzern (inklusive Administratoren) die einer Firma zugewiesen sind zu den Assets der Firma.', 'full_multiple_companies_support_text' => 'Volle Mehrmandanten-Unterstützung für Firmen', + 'scope_locations_fmcs_support_text' => 'Beschränke Standorte mit voller Mehrmandanten-Unterstützung für Firmen', + 'scope_locations_fmcs_support_help_text' => 'Bis zu Version 7.0 waren Standorte nicht auf die Firma des Benutzers beschränkt. Wenn diese Einstellung deaktiviert ist, wird die Kompatibilität zu älteren Versionen gewahrt und die Standorte nicht beschränkt. Wenn diese Einstellung aktiviert ist, werden Standorte ebenfalls auf die Firma des Benutzers beschränkt.', 'show_in_model_list' => 'In Modell-Dropdown-Liste anzeigen', 'optional' => 'optional', 'per_page' => 'Ergebnisse pro Seite', diff --git a/resources/lang/en-US/admin/settings/general.php b/resources/lang/en-US/admin/settings/general.php index 97567df8dfd4..e45c62b3ff9b 100644 --- a/resources/lang/en-US/admin/settings/general.php +++ b/resources/lang/en-US/admin/settings/general.php @@ -147,6 +147,8 @@ 'logo_print_assets_help' => 'Use branding on printable asset lists ', 'full_multiple_companies_support_help_text' => 'Restricting users (including admins) assigned to companies to their company\'s assets.', 'full_multiple_companies_support_text' => 'Full Multiple Companies Support', + 'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support', + 'scope_locations_fmcs_support_help_text' => 'Up until Version 7.0 locations were not restricted to the users company. If this setting is disabled, this preserves backward compatibility with older versions and locations are not restricted. If this setting is enabled, locations are also restricted to the users company', 'show_in_model_list' => 'Show in Model Dropdowns', 'optional' => 'optional', 'per_page' => 'Results Per Page', diff --git a/resources/views/locations/edit.blade.php b/resources/views/locations/edit.blade.php index 4b4e655a5288..1d12b1cda98e 100755 --- a/resources/views/locations/edit.blade.php +++ b/resources/views/locations/edit.blade.php @@ -17,6 +17,9 @@ @include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/users/table.manager'), 'fieldname' => 'manager_id']) + +@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id']) + @include ('partials.forms.edit.phone') @include ('partials.forms.edit.fax') diff --git a/resources/views/locations/index.blade.php b/resources/views/locations/index.blade.php index 90abda5a2d79..24f8430bd590 100755 --- a/resources/views/locations/index.blade.php +++ b/resources/views/locations/index.blade.php @@ -38,7 +38,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"] diff --git a/resources/views/locations/print.blade.php b/resources/views/locations/print.blade.php index 9f54978ff5e3..b7ee71cee2ff 100644 --- a/resources/views/locations/print.blade.php +++ b/resources/views/locations/print.blade.php @@ -53,7 +53,11 @@ @if ($parent) {{ $parent->present()->fullName() }} @endif - +
+@if ($company) + {{ trans('admin/companies/table.name') }}: {{ $company->present()->Name() }} +
+@endif @if ($manager) {{ trans('general.manager') }} {{ $manager->present()->fullName() }}
@endif diff --git a/resources/views/locations/view.blade.php b/resources/views/locations/view.blade.php index b711676464bc..aefc485ccd1d 100644 --- a/resources/views/locations/view.blade.php +++ b/resources/views/locations/view.blade.php @@ -475,6 +475,9 @@ class="table table-striped snipe-table" @if ($location->manager)
  • {{ trans('admin/users/table.manager') }}: {!! $location->manager->present()->nameUrl() !!}
  • @endif + @if ($location->company) +
  • {{ trans('admin/companies/table.name') }}: {!! $location->company->present()->nameUrl() !!}
  • + @endif @if ($location->parent)
  • {{ trans('admin/locations/table.parent') }}: {!! $location->parent->present()->nameUrl() !!}
  • @endif diff --git a/resources/views/modals/location.blade.php b/resources/views/modals/location.blade.php index 9580f4bbf936..6816f32869d4 100644 --- a/resources/views/modals/location.blade.php +++ b/resources/views/modals/location.blade.php @@ -11,6 +11,16 @@ @include('modals.partials.name', ['item' => new \App\Models\Location(), 'required' => 'true']) + + @if (($snipeSettings->scope_locations_fmcs == '1') && ($user->company)) + + @endif + + +
    + @include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id']) +
    +
    diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 099640f0b670..06bad3e50a40 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -748,6 +748,14 @@ function usersCompanyObjFilterFormatter(value, row) { } } + function locationCompanyObjFilterFormatter(value, row) { + if (value) { + return '' + row.company.name + ''; + } else { + return value; + } + } + function employeeNumFormatter(value, row) { if ((row) && (row.assigned_to) && ((row.assigned_to.employee_number))) { diff --git a/resources/views/settings/general.blade.php b/resources/views/settings/general.blade.php index 1de5d4035d85..b743e4fcdc67 100644 --- a/resources/views/settings/general.blade.php +++ b/resources/views/settings/general.blade.php @@ -54,7 +54,24 @@

    + + +
    +
    + {{ Form::label('scope_locations_fmcs', trans('admin/settings/general.scope_locations_fmcs_support_text')) }} +
    +
    + + {!! $errors->first('scope_locations_fmcs', '') !!} +

    + {{ trans('admin/settings/general.scope_locations_fmcs_support_help_text') }} +

    +
    +
    @@ -496,6 +513,5 @@ }); }); - @stop