Skip to content

Commit 2b89229

Browse files
committed
Merge remote-tracking branch 'origin/develop'
# Conflicts: # public/js/dist/all.js # public/js/dist/all.js.map # public/mix-manifest.json
2 parents bc60352 + 39791fc commit 2b89229

13 files changed

Lines changed: 217 additions & 102 deletions

File tree

app/Models/Location.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,28 @@ public function parent()
263263
->with('parent');
264264
}
265265

266+
/**
267+
* Walk up the parent chain to find the nearest ancestor with a company_id.
268+
* Used by FMCS checkout validation so that assets can be checked out to
269+
* child locations whose company is only set on a parent location.
270+
*/
271+
public function effectiveFmcsCompanyId(): ?int
272+
{
273+
if ($this->company_id) {
274+
return (int) $this->company_id;
275+
}
276+
277+
$ancestor = $this->parent()->withoutGlobalScopes()->first();
278+
while ($ancestor) {
279+
if ($ancestor->company_id) {
280+
return (int) $ancestor->company_id;
281+
}
282+
$ancestor = $ancestor->parent()->withoutGlobalScopes()->first();
283+
}
284+
285+
return null;
286+
}
287+
266288
/**
267289
* Establishes the locations -> company relationship
268290
*

app/Models/Traits/CompanyableTrait.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Models\Traits;
44

55
use App\Models\CompanyableScope;
6+
use App\Models\Location;
67
use App\Models\Setting;
78
use App\Models\User;
89
use Illuminate\Database\Eloquent\Model;
@@ -22,15 +23,25 @@ public static function bootCompanyableTrait()
2223
static::addGlobalScope(new CompanyableScope);
2324
}
2425

26+
/**
27+
* The company ID this model presents for FMCS checkout validation.
28+
* Location overrides this to walk the parent chain.
29+
*/
30+
public function effectiveFmcsCompanyId(): ?int
31+
{
32+
return $this->company_id ? (int) $this->company_id : null;
33+
}
34+
2535
/**
2636
* Whether this item may be checked out to the given target under FMCS rules.
2737
*
2838
* Returns true when:
2939
* - FMCS is disabled, OR
3040
* - this item has no company (uncompanied items are unrestricted), OR
3141
* - target is a User whose company pivot includes this item's company, OR
32-
* - target has no company and null_company_is_floater is enabled, OR
33-
* - target's company_id exactly matches this item's company_id.
42+
* - target is a Location and scope_locations_fmcs is disabled, OR
43+
* - target has no effective company and null_company_is_floater is enabled, OR
44+
* - target's effective company_id exactly matches this item's company_id.
3445
*/
3546
public function canCheckoutTo(Model $target): bool
3647
{
@@ -52,10 +63,18 @@ public function canCheckoutTo(Model $target): bool
5263
return $target->canReceiveFromCompany((int) $this->company_id);
5364
}
5465

55-
if (is_null($target->company_id)) {
66+
if ($target instanceof Location && ! $settings->scope_locations_fmcs) {
67+
return true;
68+
}
69+
70+
$targetCompanyId = method_exists($target, 'effectiveFmcsCompanyId')
71+
? $target->effectiveFmcsCompanyId()
72+
: ($target->company_id ? (int) $target->company_id : null);
73+
74+
if (is_null($targetCompanyId)) {
5675
return (bool) $settings->null_company_is_floater;
5776
}
5877

59-
return (int) $target->company_id === (int) $this->company_id;
78+
return $targetCompanyId === (int) $this->company_id;
6079
}
6180
}

app/Providers/ValidationServiceProvider.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,14 @@ public function boot()
382382
$location = Location::find($value);
383383

384384
if ($location) {
385-
if ($location->company_id === null) {
386-
// Null-company location: accessible to companied users only when floater is on.
385+
$effectiveCompanyId = $location->effectiveFmcsCompanyId();
386+
387+
if ($effectiveCompanyId === null) {
388+
// No company found anywhere in the parent chain — floater rule applies.
387389
return (bool) $settings->null_company_is_floater;
388390
}
389391

390-
if (! in_array($location->company_id, $companyIds)) {
392+
if (! in_array($effectiveCompanyId, $companyIds)) {
391393
return false;
392394
}
393395
}

public/js/dist/all.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/js/dist/all.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/mix-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"/js/dist/all.js": "/js/dist/all.js?id=571ba00068523222c399e1ec11b89288",
2+
"/js/dist/all.js": "/js/dist/all.js?id=a518646255247cadad29be09a84197bd",
33
"/css/build/overrides.css": "/css/build/overrides.css?id=9f70623c0b56ef8ecec6fb859344d279",
44
"/css/build/app.css": "/css/build/app.css?id=3cfdd1824004111aea48a50793be2518",
55
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=bdf169bc2141f453390614c138cdce95",

resources/assets/js/snipeit_modals.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ $(function () {
100100

101101

102102

103+
$(document).on('click', '#createModal .toggle-password', function () {
104+
$(this).toggleClass('fa-eye fa-eye-slash');
105+
var input = $($(this).attr('data-toggle'));
106+
input.attr('type', input.attr('type') === 'password' ? 'text' : 'password');
107+
});
108+
103109
$('#createModal').on('click','#modal-save', function () {
104110
$.ajax({
105111
type: 'POST',
Lines changed: 85 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
{{-- See snipeit_modals.js for what powers this --}}
22

33
<script nonce="{{ csrf_token() }}">
4-
54
window.setTimeout(function () {
65
$('#modal-genPassword').pGenerator({
76
'bind': 'click',
87
'passwordElement': '#modal-password',
9-
'displayElement': '#modal-generated-password',
108
'passwordLength': 16,
119
'uppercase': true,
1210
'lowercase': true,
@@ -16,7 +14,7 @@
1614
$('#modal-password_confirmation').val($('#modal-password').val());
1715
}
1816
});
19-
}, 1000);
17+
}, 500);
2018
</script>
2119

2220
<div class="modal-dialog">
@@ -25,97 +23,105 @@
2523
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
2624
<h2 class="modal-title">{{ trans('admin/users/table.createuser') }}</h2>
2725
</div>
28-
<div class="modal-body" style="width:100%; display:block;">
29-
<form action="{{ route('api.users.store') }}" onsubmit="return false">
30-
<div class="alert alert-danger" id="modal_error_msg" style="display:none">
31-
</div>
32-
<!-- Setup of default company, taken from asset creator -->
33-
@if ($user->companies->isNotEmpty())
34-
<input type="hidden" name="company_id" id='modal-company' value='{{ $user->companies->first()->id }}' class="form-control">
35-
@endif
36-
37-
<!-- Select company, only for users with multicompany access - replace default company -->
38-
<div class="dynamic-form-row">
39-
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
40-
</div>
41-
<div class="dynamic-form-row">
42-
@include ('partials.forms.edit.location-profile-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
43-
</div>
44-
<div class="dynamic-form-row">
45-
@include('partials.forms.edit.name-first', ['value' => '', 'required' => 'true', 'class' => 'col-md-8 col-xs-12-pull', 'style' => 'width:67%;'])
46-
</div>
47-
<div class="dynamic-form-row">
48-
@include('partials.forms.edit.name-last', ['value' => '', 'required' => 'true', 'class' => 'col-md-8 col-xs-12-pull', 'style' => 'width:67%;'])
49-
</div>
50-
<div class="dynamic-form-row">
51-
@include('partials.forms.edit.email')
52-
</div>
53-
<div class="dynamic-form-row">
54-
@include('partials.forms.edit.username', [ 'required' => 'true'])
55-
</div>
26+
<div class="modal-body">
27+
<form action="{{ route('api.users.store') }}" onsubmit="return false">
28+
<div class="alert alert-danger" id="modal_error_msg" style="display:none"></div>
5629

57-
<div class="dynamic-form-row">
58-
<div class="form-group">
59-
<div class="col-md-3 col-xs-12">
60-
<label class="control-label" for="modal-password">{{ trans('admin/users/table.password') }}:</label>
61-
</div>
62-
<div class="col-md-7 col-xs-12">
63-
<input type='password' name="password" id='modal-password' class="form-control" required>
64-
<div id="modal-generated-password"></div>
65-
</div>
66-
<div class="col-md-1">
67-
<a href="#" class="btn btn-default btn-sm" id="modal-genPassword" data-tooltip="true" title="{{ trans('admin/users/general.generate_password') }}">
68-
<i class="fa-solid fa-wand-magic-sparkles"></i>
69-
</a>
70-
<div id="modal-generated-password"></div>
71-
</div>
72-
</div>
30+
@if ($user->companies->isNotEmpty())
31+
<input type="hidden" name="company_id" value="{{ $user->companies->first()->id }}">
32+
@endif
33+
34+
<div class="dynamic-form-row">
35+
<div class="col-md-3 col-xs-12"><label for="modal-company_id">{{ trans('general.company') }}:</label></div>
36+
<div class="col-md-9 col-xs-12">
37+
<select class="js-data-ajax" data-endpoint="companies" data-placeholder="{{ trans('general.select_company') }}" name="company_id" id="modal-company_id" style="width:100%">
38+
<option value=""></option>
39+
</select>
7340
</div>
41+
</div>
7442

75-
<div class="dynamic-form-row">
76-
<div class="form-group">
77-
<div class="col-md-3 col-xs-12 ">
78-
<label class="control-label" for="modal-password_confirmation">{{ trans('admin/users/table.password_confirm') }}:</label>
79-
</div>
80-
<div class="col-md-7 col-xs-12">
81-
<input class="form-control" type='password' name="password_confirmation" id='modal-password_confirmation' required>
82-
</div>
83-
</div>
43+
<div class="dynamic-form-row">
44+
<div class="col-md-3 col-xs-12"><label for="modal-location_id">{{ trans('general.location') }}:</label></div>
45+
<div class="col-md-9 col-xs-12">
46+
<select class="js-data-ajax" data-endpoint="locations" data-placeholder="{{ trans('general.select_location') }}" name="location_id" id="modal-location_id" style="width:100%">
47+
<option value=""></option>
48+
</select>
8449
</div>
50+
</div>
8551

86-
<div class="dynamic-form-row">
87-
<div class="form-group">
88-
<div class="col-md-3 col-xs-12 ">
89-
<label class="control-label" for="modal-display_name">{{ trans('admin/users/table.display_name') }}:</label>
90-
</div>
91-
<div class="col-md-8 col-xs-12">
92-
<input class="form-control" type='text' name="display_name" id='modal-display_name' required>
52+
<div class="dynamic-form-row">
53+
<div class="col-md-3 col-xs-12"><label for="modal-first_name">{{ trans('general.first_name') }}:</label></div>
54+
<div class="col-md-9 col-xs-12"><input type="text" name="first_name" id="modal-first_name" class="form-control" maxlength="191" required></div>
55+
</div>
9356

94-
</div>
57+
<div class="dynamic-form-row">
58+
<div class="col-md-3 col-xs-12"><label for="modal-last_name">{{ trans('general.last_name') }}:</label></div>
59+
<div class="col-md-9 col-xs-12"><input type="text" name="last_name" id="modal-last_name" class="form-control" maxlength="191" required></div>
60+
</div>
61+
62+
<div class="dynamic-form-row">
63+
<div class="col-md-3 col-xs-12"><label for="modal-email">{{ trans('admin/users/table.email') }}:</label></div>
64+
<div class="col-md-9 col-xs-12"><input type="email" name="email" id="modal-email" class="form-control" maxlength="191"></div>
65+
</div>
66+
67+
<div class="dynamic-form-row">
68+
<div class="col-md-3 col-xs-12"><label for="modal-username">{{ trans('admin/users/table.username') }}:</label></div>
69+
<div class="col-md-9 col-xs-12"><input type="text" name="username" id="modal-username" class="form-control" maxlength="191" required></div>
70+
</div>
71+
72+
<div class="dynamic-form-row">
73+
<div class="col-md-3 col-xs-12"><label for="modal-password">{{ trans('admin/users/table.password') }}:</label></div>
74+
<div class="col-md-8 col-xs-12">
75+
<div class="input-group">
76+
<input type="password" name="password" id="modal-password" class="form-control" required>
77+
<span class="input-group-addon">
78+
<i data-toggle="#modal-password" class="fa fa-fw fa-eye toggle-password" aria-hidden="true"></i>
79+
<span class="sr-only">{{ trans('general.toggle_password_visibility') }}</span>
80+
</span>
9581
</div>
9682
</div>
83+
<div class="col-md-1">
84+
<a href="#" class="btn btn-default btn-sm" id="modal-genPassword" data-tooltip="true" title="{{ trans('admin/users/general.generate_password') }}">
85+
<i class="fa-solid fa-wand-magic-sparkles"></i>
86+
</a>
87+
</div>
88+
</div>
9789

98-
<!-- Checkbox for activation new user, by default set for activated -->
99-
<div class="dynamic-form-row">
100-
<div class="form-group">
101-
<div class="col-md-offset-3 col-md-8 col-xs-12">
102-
<label class="form-control">
103-
<input type="checkbox" value="1" name="activated" id="modal-activated" {{ (old('activated', $user->activated)) == '1' ? ' checked="checked"' : '' }} aria-label="activated">
104-
{{ trans('general.login_enabled') }}
105-
</label>
106-
</div>
90+
<div class="dynamic-form-row">
91+
<div class="col-md-3 col-xs-12"><label for="modal-password_confirmation">{{ trans('admin/users/table.password_confirm') }}:</label></div>
92+
<div class="col-md-9 col-xs-12">
93+
<div class="input-group">
94+
<input type="password" name="password_confirmation" id="modal-password_confirmation" class="form-control" required>
95+
<span class="input-group-addon">
96+
<i data-toggle="#modal-password_confirmation" class="fa fa-fw fa-eye toggle-password" aria-hidden="true"></i>
97+
<span class="sr-only">{{ trans('general.toggle_password_visibility') }}</span>
98+
</span>
10799
</div>
108100
</div>
109-
<div class="dynamic-form-row">
110-
@include('modals.partials.footer')
101+
</div>
102+
103+
<div class="dynamic-form-row">
104+
<div class="col-md-3 col-xs-12"><label for="modal-display_name">{{ trans('admin/users/table.display_name') }}:</label></div>
105+
<div class="col-md-9 col-xs-12"><input type="text" name="display_name" id="modal-display_name" class="form-control" maxlength="191"></div>
106+
</div>
107+
108+
<div class="dynamic-form-row">
109+
<div class="col-md-offset-3 col-md-9">
110+
<label class="form-control">
111+
<input type="checkbox" value="1" name="activated" id="modal-activated" checked aria-label="activated">
112+
{{ trans('general.login_enabled') }}
113+
</label>
111114
</div>
112-
</form>
113-
</div>
115+
</div>
116+
117+
</form>
118+
</div>
119+
@include('modals.partials.footer')
114120
</div><!-- /.modal-content -->
115121
</div><!-- /.modal-dialog -->
116122

117123
<script>
118-
$(document).ready(function(){
124+
$(document).ready(function () {
119125
$('#modal-first_name').focus();
120126
});
121-
</script>
127+
</script>

0 commit comments

Comments
 (0)