Skip to content

FMCS/Console: Fixed #19166 scope check updates for multiple companies, adds floater - #19167

Merged
snipe merged 25 commits into
developfrom
fmcs-scope-check-updates-for-multiple-companies
Jun 13, 2026
Merged

snipe merged 25 commits into
developfrom
fmcs-scope-check-updates-for-multiple-companies

Conversation

@snipe

@snipe snipe commented Jun 10, 2026

Copy link
Copy Markdown
Member

This does a little more than I'd like in one PR, but I think it will be worth it.

This updates the FMCS Location Scope checker to consider user companies, and also adds a setting for "floater", which modifies the behavior of how the constraints work.

We see two common use-cases....

  1. Folks want to things with null categories to only be check-out-able to things with null companies
  2. Folks want to be able to use "uncompanied" items as a floater company.

While creating a "Null Company" company possible (as a sort of floater container company), I hate it, so I figured I'd give this a shot.

Floater mode (null_company_is_floater setting) — a new checkbox in General Settings. When enabled, users and items with no company assignment are treated as "floaters" that can be checked out to/from any company. When disabled (strict mode), null-company items are treated as their own pseudo-company and blocked from cross-company operations.

Screenshot 2026-06-10 at 12 45 59 PM

The canReceiveFromCompany() check on User queries all pivot entries rather than only the primary company_id (which is no longer used on users), so a user assigned to companies A and C can receive an asset from either company.

Non-User checkout targets - the FMCS company mismatch check now applies consistently to Location and Asset targets (not just User), using the same floater logic.

Translated error messages - all FMCS checkout rejections now produce specific, translated error messages naming the item, its company, and the target (e.g. Asset "MacBook Pro" cannot be checked out to User "jsmith" - Asset belongs to Acme Corp). Previously these were hard-coded English strings or the generic error_user_company message. We might decide to make those a bit less wordy and more generic, but we'll see.

Helper::test_locations_fmcs() - updated to use the same three-way floater logic (both null = never mismatch; one null = mismatch only in strict mode; both non-null different = always mismatch).

Testing

Prerequisites

  • FMCS must be enabled: Admin → General Settings → Full Multiple Company Support = ON
  • Have at least two companies created (e.g. Acme and Globex)
  • Have assets, accessories, licenses, consumables, and components assigned to Acme

1. Asset Checkout — Cross-Company Blocked (Strict Mode)

Floater mode OFF

  1. Create a user assigned to Globex only
  2. Attempt to check out an Acme asset to that user
  3. Expect: redirect back with an error message like:

    Asset "MacBook Pro" cannot be checked out to User "jsmith" - Asset belongs to Acme

  4. Confirm the asset remains unassigned

2. Asset Checkout - Multi-Company User via Pivot

Floater mode OFF

  1. Create a user whose primary company is Globex, but who also belongs to Acme via the Companies field on their profile
  2. Attempt to check out an Acme asset to that user
  3. Expect: checkout succeeds

Then repeat with a user who belongs to Globex only:

  1. Attempt the same checkout
  2. Expect: blocked with a specific error message

3. Asset Checkout - Location and Asset Targets

Floater mode OFF

  1. Create a Location assigned to Globex
  2. Check out an Acme asset to that location
  3. Expect: blocked with an error naming the location and company
  4. Create a child asset assigned to Globex
  5. Check out an Acme asset to that child asset
  6. Expect: blocked with an error naming the asset and company

4. Floater Mode - Null-Company User Can Receive Anything

Floater mode ON

  1. Create a user with no company assigned
  2. Check out an Acme asset to that user
  3. Expect: checkout succeeds

Floater mode OFF (strict)

  1. Repeat steps 1–2
  2. Expect: blocked with an error message

5. Accessories, Licenses, Consumables, Components

Repeat the cross-company block check for each item type, confirming:

  • The error message names the specific item type and name (e.g. Accessory "USB Hub", License "Office 365")
  • The error names the item's company and the target's name
  • No generic or untranslated error appears

6. Location Scoped-Items Error

scope_locations_fmcs must be enabled alongside FMCS

  1. Create a location assigned to Acme with a user assigned to Globex in it
  2. Edit that location and try to change its company to a third company (e.g. Initech)
  3. Expect: blocked with a message naming the mismatched item, its type, and company — e.g.:

    User "jsmith" (Company: Globex) does not match this location's company


7. Location Parent Company Error

  1. Create Location A assigned to Acme
  2. Create Location B assigned to Globex
  3. Edit Location B and set Location A as its parent
  4. Expect: blocked with a message like:

    Parent location "Location A" belongs to Acme, but this location belongs to Globex


8. fmcs_location Validation on User/Asset Save

scope_locations_fmcs must be enabled alongside FMCS

  1. Create a location assigned to Acme
  2. Edit a Globex user and set their location to that Acme location
  3. Expect: validation error naming the specific location and its company - no raw :location or :location_company placeholders visible

9. Regression — Non-FMCS Installs Unaffected

  1. Turn FMCS off entirely
  2. Check out an asset from one company to a user from another
  3. Expect: checkout succeeds - no company checking should occur

This can get a bit tricky to test, since we deliberately block you from enabling location scoping (on purpose!) if you have mismatches, but these changes should also apply to php artisan snipeit:test-locations-fmcs. You might have to twiddle that flag directly in the database in order to see what it does.

(I still want to tidy that script output up a bit, but this will do for now.)

Without Floater Setting Enabled

Screenshot 2026-06-10 at 12 58 10 PM

With Floater Setting Enabled

Here it should report no issues, since the nulled company items are floaters.

Screenshot 2026-06-10 at 12 58 05 PM

This should fix #19166 and #19163

@snipe
snipe requested a review from uberbrady June 10, 2026 12:03
@snipe snipe added the 🆘 testers-needed This is a feature/bugfix that has been completed but needs testing. label Jun 10, 2026
@codacy-production

codacy-production Bot commented Jun 10, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 medium · 1 minor

Alerts:
⚠ 3 issues (≤ 0 issues of at least minor severity)

Results:
3 new issues

Category Results
UnusedCode 1 medium
CodeStyle 1 minor
Complexity 1 medium

View in Codacy

🟢 Metrics 36 complexity

Metric Results
Complexity 36

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Comment thread app/Http/Controllers/Assets/AssetCheckoutController.php Outdated
Comment thread app/Http/Controllers/Assets/BulkAssetsController.php Outdated

@uberbrady uberbrady left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely some very good work, but there a are a few technical problems that I'm worried about, but more importantly, there is a big structural problem that I would love to see fixed.

So let's start with the major issue here - I think conflating the new treatment of null company doesn't belong in this PR. I think we should really split these out into two concerns, because it's hard, when reading this PR, to see where one thing starts, and another stops.

The other small bits I saw were mostly around variable naming, nested ternaries (which I strongly recommend against), but the general vibe I'm getting is that the stuff that specifically fixes the problem we're trying to fix looks good. I can take another pass at it when I have power I have battery as well.

Thank you for aggressively looking at solving this problem.

if ($fmcsTarget && $license->company_id && $license->company_id !== $fmcsTarget->company_id) {
return redirect()->route('licenses.index')->with('error', trans('general.error_user_company'));
if ($fmcsTarget && $license->company_id) {
$mismatch = is_null($fmcsTarget->company_id)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're using this phrase a few times, is this maybe a thing we can put on Companyable, or maybe the individualized companyable things?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean here. What's "this"?

// Backward compatibility for locations makes no sense without FullMultipleCompanySupport
// These options make no sense without FullMultipleCompanySupport
if (! $setting->full_multiple_companies_support) {
$setting->scope_locations_fmcs = '0';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of stringed '0' - can we just use false or true here? This feels a little sketchy, a stringed integer. Triple-equals (===) will definitely fail for false.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(But regular single-equals or not-equals (!= or ==) will work the way you expect - but this is setting us up for confusion.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely could, but this is the behavior through the whole system IIRC, so it would be confusing to change it here. A later PR can change that across the boards tho.

Comment thread app/Models/Company.php

@uberbrady uberbrady left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple more things I did notice - I don't think any of these are fatal, I'm just arguing for versions that are a little bit easier to read/parse as someone who might be troubleshooting this in the near, or maybe even distant, future.

Comment thread app/Http/Controllers/LocationsController.php Outdated
Comment thread app/Http/Controllers/Api/LocationsController.php Outdated
Comment thread app/Http/Controllers/Api/LocationsController.php Outdated
Comment thread app/Http/Controllers/Api/UsersController.php Outdated
Comment thread app/Http/Controllers/Assets/AssetCheckoutController.php Outdated
Comment thread app/Models/Company.php
Comment thread app/Models/Company.php Outdated
@bzeus

bzeus commented Jun 12, 2026

Copy link
Copy Markdown
Member

So far, most of the tests are doing exactly what they should.

  1. Location Parent Company Error is not. It did not give me the expected error message.

@bzeus

bzeus commented Jun 12, 2026

Copy link
Copy Markdown
Member

I need to revisit 6. and 8.

I had too many conflicts in my regular local branch, so need to figure that out and then go back.

Everything else (except 7) looks good

@snipe

snipe commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

@bzeus once you get your local sorted, be sure to pull from this branch again, as I've made a few more changes

@snipe

snipe commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Location Parent Company Error is not. It did not give me the expected error message.

@bzeus could you be more specific? Did it give you any error message? Did it fail, etc? What actually happened.

@snipe
snipe merged commit e2bea57 into develop Jun 13, 2026
8 of 9 checks passed
@snipe
snipe deleted the fmcs-scope-check-updates-for-multiple-companies branch June 13, 2026 13:51
@bzeus

bzeus commented Jun 15, 2026

Copy link
Copy Markdown
Member

Location Parent Company Error is not. It did not give me the expected error message.

@bzeus could you be more specific? Did it give you any error message? Did it fail, etc? What actually happened.

It didn't block the location edit, as it was supposed to, so the edit went through

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆘 testers-needed This is a feature/bugfix that has been completed but needs testing.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants