Fixed #19112 - company list disabled

This commit is contained in:
snipe
2026-05-29 09:13:03 +01:00
parent 135db70b0f
commit 9b522b69ff
20 changed files with 125760 additions and 70 deletions
@@ -609,6 +609,11 @@ class AssetsController extends Controller
])->with('model', 'status', 'assignedTo')
->NotArchived();
// When FMCS is enabled, automatically scope to companies the acting user belongs to.
// scopeCompanyables is a no-op for superusers and when FMCS is disabled.
$assets = Company::scopeCompanyables($assets);
// Allow further narrowing to a specific company passed via data-company-id on the select.
if ((Setting::getSettings()->full_multiple_companies_support == '1') && $request->filled('companyId')) {
$companyIds = array_values(array_filter(array_map('intval', explode(',', $request->input('companyId')))));
if (! empty($companyIds)) {
@@ -9,6 +9,7 @@ use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\CompaniesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
@@ -206,6 +207,16 @@ class CompaniesController extends Controller
'companies.tag_color',
]);
// When FMCS is enabled and the user is not a superuser, restrict the list to
// companies they belong to (primary company_id + pivot companies). This lets
// non-superusers select a company from their own set when creating assets, etc.
if (Setting::getSettings()->full_multiple_companies_support == '1' && ! auth()->user()->isSuperUser()) {
$userCompanyIds = auth()->user()->allCompanies()->pluck('id');
if ($userCompanyIds->isNotEmpty()) {
$companies->whereIn('companies.id', $userCompanyIds);
}
}
if ($request->filled('search')) {
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->input('search').'%');
}
@@ -396,6 +396,11 @@ class UsersController extends Controller
]
)->where('show_in_list', '=', '1');
// When FMCS is enabled, automatically scope to companies the acting user belongs to.
// scopeCompanyables is a no-op for superusers and when FMCS is disabled.
$users = Company::scopeCompanyables($users, 'company_id', 'users');
// Allow further narrowing to a specific company passed via data-company-ids on the select.
if ((Setting::getSettings()->full_multiple_companies_support == '1') && $request->filled('companyId')) {
$companyIds = array_values(array_filter(array_map('intval', explode(',', $request->input('companyId')))));
if (! empty($companyIds)) {
+10
View File
@@ -656,6 +656,16 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return false;
}
/**
* Returns all companies this user belongs to — union of the primary company_id
* column and the many-to-many pivot — as a deduplicated Collection.
* Used to scope FMCS dropdowns to companies the user is allowed to work with.
*/
public function allCompanies(): Collection
{
return $this->companies->push($this->company)->filter()->unique('id')->values();
}
/**
* Sync company pivot membership and log the change if the set of companies changed.
*
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1645 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+24718 -6
View File
File diff suppressed because one or more lines are too long
+414 -1
View File
File diff suppressed because one or more lines are too long
+135 -1
View File
@@ -1 +1,135 @@
#signature-pad{padding-top:250px;margin:auto}.m-signature-pad{position:relative;font-size:10px;width:100%;height:300px;border:1px solid #e8e8e8;background-color:#fff;box-shadow:0 1px 4px rgba(0,0,0,.27),0 0 40px rgba(0,0,0,.08) inset;border-radius:4px}.m-signature-pad:after,.m-signature-pad:before{position:absolute;z-index:-1;content:"";width:40%;height:10px;left:20px;bottom:10px;background:0 0;-webkit-transform:skew(-3deg) rotate(-3deg);-moz-transform:skew(-3deg) rotate(-3deg);-ms-transform:skew(-3deg) rotate(-3deg);-o-transform:skew(-3deg) rotate(-3deg);transform:skew(-3deg) rotate(-3deg);box-shadow:0 8px 12px rgba(0,0,0,.4)}.m-signature-pad:after{left:auto;right:20px;-webkit-transform:skew(3deg) rotate(3deg);-moz-transform:skew(3deg) rotate(3deg);-ms-transform:skew(3deg) rotate(3deg);-o-transform:skew(3deg) rotate(3deg);transform:skew(3deg) rotate(3deg)}.m-signature-pad--body{position:absolute;top:20px;bottom:60px;border:1px solid #f4f4f4;background-color:#fff}.m-signature-pad--body canvas{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px rgba(0,0,0,.02) inset}.m-signature-pad--footer{position:absolute;left:20px;right:20px;bottom:20px;height:40px}.m-signature-pad--footer .description{color:#c3c3c3;text-align:center;font-size:1.2em;margin-top:1.8em}.m-signature-pad--footer .button{position:absolute;bottom:0}.m-signature-pad--footer .button.clear{left:0}.m-signature-pad--footer .button.save{right:0}@media screen and (max-width:1024px){.m-signature-pad{top:0;left:0;right:0;bottom:0;width:auto;height:auto;min-width:250px;min-height:140px;margin:5%}}@media screen and (min-device-width:768px) and (max-device-width:1024px){.m-signature-pad{margin:10%}}@media screen and (max-height:320px){.m-signature-pad--body{left:0;right:0;top:0;bottom:32px}.m-signature-pad--footer{left:20px;right:20px;bottom:4px;height:28px}.m-signature-pad--footer .description{font-size:1em;margin-top:1em}}
#signature-pad {
padding-top: 250px;
margin: auto;
}
.m-signature-pad {
position: relative;
font-size: 10px;
width: 100%;
height: 300px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
border-radius: 4px;
}
.m-signature-pad:before, .m-signature-pad:after {
position: absolute;
z-index: -1;
content: "";
width: 40%;
height: 10px;
left: 20px;
bottom: 10px;
background: transparent;
-webkit-transform: skew(-3deg) rotate(-3deg);
-moz-transform: skew(-3deg) rotate(-3deg);
-ms-transform: skew(-3deg) rotate(-3deg);
-o-transform: skew(-3deg) rotate(-3deg);
transform: skew(-3deg) rotate(-3deg);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4);
}
.m-signature-pad:after {
left: auto;
right: 20px;
-webkit-transform: skew(3deg) rotate(3deg);
-moz-transform: skew(3deg) rotate(3deg);
-ms-transform: skew(3deg) rotate(3deg);
-o-transform: skew(3deg) rotate(3deg);
transform: skew(3deg) rotate(3deg);
}
.m-signature-pad--body {
position: absolute;
top: 20px;
bottom: 60px;
border: 1px solid #f4f4f4;
background-color: white;
}
.m-signature-pad--body
canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
}
.m-signature-pad--footer {
position: absolute;
left: 20px;
right: 20px;
bottom: 20px;
height: 40px;
}
.m-signature-pad--footer
.description {
color: #C3C3C3;
text-align: center;
font-size: 1.2em;
margin-top: 1.8em;
}
.m-signature-pad--footer
.button {
position: absolute;
bottom: 0;
}
.m-signature-pad--footer
.button.clear {
left: 0;
}
.m-signature-pad--footer
.button.save {
right: 0;
}
@media screen and (max-width: 1024px) {
.m-signature-pad {
top: 0;
left: 0;
right: 0;
bottom: 0;
width: auto;
height: auto;
min-width: 250px;
min-height: 140px;
margin: 5%;
}
}
@media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
.m-signature-pad {
margin: 10%;
}
}
@media screen and (max-height: 320px) {
.m-signature-pad--body {
left: 0;
right: 0;
top: 0;
bottom: 32px;
}
.m-signature-pad--footer {
left: 20px;
right: 20px;
bottom: 4px;
height: 28px;
}
.m-signature-pad--footer
.description {
font-size: 1em;
margin-top: 1em;
}
}
+53234 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+38849 -1
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -1,9 +1,9 @@
{
"/js/dist/all.js": "/js/dist/all.js?id=df32def1d4704b4b2bbd47d8fd68aca1",
"/css/build/overrides.css": "/css/build/overrides.css?id=9bfab28a94932d45568ad50f3c6c5e2c",
"/css/build/app.css": "/css/build/app.css?id=4b2abd7fa3560ada549e9d08bd836aa8",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=bdf169bc2141f453390614c138cdce95",
"/css/dist/all.css": "/css/dist/all.css?id=f5f404325dedd1abd00dc781664c0034",
"/js/dist/all.js": "/js/dist/all.js?id=237590d8c3f4b9a4c8c504e416bdf75a",
"/css/build/overrides.css": "/css/build/overrides.css?id=c173dd71d56c1089bf560a849586d93e",
"/css/build/app.css": "/css/build/app.css?id=63ef76491d01db361ad53cf1c8c7114f",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=ee0ed88465dd878588ed044eefb67723",
"/css/dist/all.css": "/css/dist/all.css?id=57e6bf27bcfad47e58a82b9842a7d5bd",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
+9 -2
View File
@@ -468,8 +468,15 @@ $(function () {
syncCheckoutToTypeUi(true);
});
// Apply the current radio selection on initial render.
syncCheckoutToTypeUi(false);
// Apply the current radio selection on initial render, but only when the
// selector row itself is already visible. On the asset create page the selector
// starts hidden (display:none) and user_add() reveals it after a deployability
// AJAX check — running here would prematurely show a panel before the radio
// group is visible. On the standalone checkout page the selector is visible
// from the start, so the sync runs normally there.
if ($('#assignto_selector').is(':visible')) {
syncCheckoutToTypeUi(false);
}
});
+10 -5
View File
@@ -100,9 +100,9 @@
@include ('partials.forms.edit.status', [ 'required' => 'true'])
@if (!$item->id)
@include ('partials.forms.checkout-selector', ['user_select' => 'true','asset_select' => 'true', 'location_select' => 'true', 'style' => 'display:none;'])
@include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/hardware/form.checkout_to'), 'fieldname' => 'assigned_user', 'style' => 'display:none;', 'required' => 'false'])
@include ('partials.forms.edit.asset-select', ['translated_name' => trans('admin/hardware/form.checkout_to'), 'fieldname' => 'assigned_asset', 'style' => 'display:none;', 'required' => 'false'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('admin/hardware/form.checkout_to'), 'fieldname' => 'assigned_location', 'style' => 'display:none;', 'required' => 'false'])
@include ('partials.forms.edit.user-select', ['translated_name' => trans('general.user'), 'fieldname' => 'assigned_user', 'style' => 'display:none;', 'required' => 'false'])
@include ('partials.forms.edit.asset-select', ['translated_name' => trans('general.asset'), 'fieldname' => 'assigned_asset', 'style' => 'display:none;', 'required' => 'false'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'assigned_location', 'style' => 'display:none;', 'required' => 'false'])
@endif
@include ('partials.forms.edit.notes')
@@ -282,16 +282,21 @@
$("#selected_status_status").fadeIn();
if (data == true) {
var checkoutType = $('input[name=checkout_to_type]:checked').val() || 'user';
$("#assignto_selector").show();
$("#assigned_user").show();
$("#assigned_user").toggle(checkoutType === 'user');
$("#assigned_asset").toggle(checkoutType === 'asset');
$("#assigned_location").toggle(checkoutType === 'location');
$("#selected_status_status").removeClass('text-danger');
$("#selected_status_status").addClass('text-success');
$("#selected_status_status").html('<x-icon type="checkmark" /> {{ trans_choice('admin/hardware/form.asset_deployable', 1)}}');
} else {
$("#assignto_selector").hide();
$("#assigned_user").hide();
$("#assigned_asset").hide();
$("#assigned_location").hide();
$("#selected_status_status").removeClass('text-success');
$("#selected_status_status").addClass('text-danger');
$("#selected_status_status").html('<x-icon type="warning" /> {{ (($item->assigned_to!='') && ($item->assigned_type!='') && ($item->deleted_at == '')) ? trans('admin/hardware/form.asset_not_deployable_checkin') : trans('admin/hardware/form.asset_not_deployable') }} ');
@@ -1,47 +1,27 @@
<!-- Company -->
@if (($snipeSettings->full_multiple_companies_support=='1') && (!Auth::user()->isSuperUser()))
<!-- full company support is enabled and this user isn't a superadmin -->
<div class="form-group">
<label for="{{ $fieldname }}" class="col-md-3 control-label">{{ $translated_name }}</label>
<div class="col-md-6">
<select class="js-data-ajax" disabled data-endpoint="companies"
data-placeholder="{{ trans('general.select_company') }}" name="{{ $fieldname }}{{ (isset($multiple) && ($multiple=='true')) ? '[]' : '' }}" style="width: 100%"
aria-label="{{ $fieldname }}"{{ (isset($multiple) && ($multiple=='true')) ? " multiple='multiple'" : '' }}>
<!-- When FMCS is enabled the companies selectlist API automatically scopes results to
the current user's companies (primary + pivot), so no separate disabled branch is needed. -->
<div id="{{ $fieldname }}" class="form-group{{ $errors->has($fieldname) ? ' has-error' : '' }}">
<label for="{{ $fieldname }}" class="col-md-3 control-label">{{ $translated_name }}</label>
<div class="col-md-6">
<select class="js-data-ajax" data-endpoint="companies" data-placeholder="{{ trans('general.select_company') }}" name="{{ $fieldname }}{{ (isset($multiple) && ($multiple=='true')) ? '[]' : '' }}" style="width: 100%"{{ (isset($multiple) && ($multiple=='true')) ? " multiple='multiple'" : '' }}>
@isset ($selected)
@foreach ($selected as $company_id)
<option value="{{ $company_id }}" selected="selected" role="option" aria-selected="true">
{{ \App\Models\Company::find($company_id)->name }}
</option>
@endforeach
@endisset
@if (!isset($multiple) || $multiple !== 'true')
@if ($company_id = old($fieldname, (isset($item)) ? $item->{$fieldname} : ''))
<option value="{{ $company_id }}" selected="selected" role="option" aria-selected="true" role="option">
<option value="{{ $company_id }}" selected="selected">
{{ (\App\Models\Company::find($company_id)) ? \App\Models\Company::find($company_id)->name : '' }}
</option>
@else
{!! (!isset($multiple) || ($multiple=='false')) ? '<option value="" role="option">'.trans('general.select_company').'</option>' : '' !!}
<option value="" role="option">{{ trans('general.select_company') }}</option>
@endif
</select>
</div>
@endif
</select>
</div>
@else
<!-- full company support is enabled or this user is a superadmin -->
<div id="{{ $fieldname }}" class="form-group{{ $errors->has($fieldname) ? ' has-error' : '' }}">
<label for="{{ $fieldname }}" class="col-md-3 control-label">{{ $translated_name }}</label>
<div class="col-md-6">
<select class="js-data-ajax" data-endpoint="companies" data-placeholder="{{ trans('general.select_company') }}" name="{{ $fieldname }}{{ (isset($multiple) && ($multiple=='true')) ? '[]' : '' }}" style="width: 100%"{{ (isset($multiple) && ($multiple=='true')) ? " multiple='multiple'" : '' }}>
@isset ($selected)
@foreach ($selected as $company_id)
<option value="{{ $company_id }}" selected="selected" role="option" aria-selected="true">
{{ \App\Models\Company::find($company_id)->name }}
</option>
@endforeach
@endisset
@if (!isset($multiple) || $multiple !== 'true')
@if ($company_id = old($fieldname, (isset($item)) ? $item->{$fieldname} : ''))
<option value="{{ $company_id }}" selected="selected">
{{ (\App\Models\Company::find($company_id)) ? \App\Models\Company::find($company_id)->name : '' }}
</option>
@else
<option value="" role="option">{{ trans('general.select_company') }}</option>
@endif
@endif
</select>
</div>
{!! $errors->first($fieldname, '<div class="col-md-8 col-md-offset-3"><span class="alert-msg"><i class="fas fa-times" aria-hidden="true"></i> :message</span></div>') !!}
</div>
@endif
{!! $errors->first($fieldname, '<div class="col-md-8 col-md-offset-3"><span class="alert-msg"><i class="fas fa-times" aria-hidden="true"></i> :message</span></div>') !!}
</div>