Skip to content

Moving an Asset Between Companies Developer Guide

Ed Mozley edited this page Oct 4, 2026 · 2 revisions

Moving an asset between companies - developer guide

User-facing page: Moving an asset between companies. The asset twin of moving a task between companies; read that first, because the access rule is the same and so is the reason for it.


The move: AssetsService::moveToCompany()

Endpoint api/assets/move_to_company.php is a thin adapter; every rule is in the service.

Access, both ways, exactly as the task move: $ctx->companyScope (what this caller may touch) and analystCanAccessTenant() (what the person may reach). For a session they agree; for an API key issued for one company they do not. "No access" and "no such company" give one message, so probing ids cannot reveal which companies exist.

An asset carries more company-owned things than a task. They split into two kinds on purpose:

What Why
Cleared, audited, named in the toast location_id unless visible to the target (its own or shared); asset_type_id / asset_status_id unless in getTenantConfigRows() for the target They do not exist in the new company. Keeping them would leave the asset wearing a value its own company cannot see or pick.
Refused, naming the problem a hostname or asset_tag the target already uses; a person holder (users_assets.user_id) whose company differs Both identifiers are unique per company, and the inventory agent falls back to the hostname when a machine has no usable serial (reconciliation). An Intune link survives the move (3.1.0). A holder is a decision for a person, not a guess.

Analyst holders move with it: analysts are not company-scoped. The Default company is stored as NULL, and resolved to its real id before comparing, so a Default asset "moved" into Default is a no-op, not a nonsense audit line.

One transaction; one company audit row plus one per cleared field (asset_history, keys shared with the history view's labels).

Company on create

create_asset.php takes an optional tenant_id, checked the same two ways. It also checks the type, status and location belong to that company. The form loads them for the chosen company, so this only fires on a stale form or a crafted request, but without it an asset could be created in one company wearing another's private location.

The form's lists reload through a new ?for_tenant= parameter on get_asset_types.php, get_asset_status_types.php and get_asset_locations.php, resolved by requestedTenantId() in includes/tenancy.php. πŸ”΄ An id the analyst cannot reach is ignored, not honoured: the worst a crafted request gets is the active company's lists. The settings view (?manage=1) never uses it.

The response carries in_active and company_name, so the form opens the new asset when it is in the company on screen, and otherwise says where it went.

⚠️ The asset list is NOT widened for All companies

Tickets β†’ Users and the Tasks board now widen under All companies (see All companies view - Developer Guide). The asset list deliberately does not. Its type, status and location dropdowns are single-company, so a widened list would let you open another company's asset and give it a location from the wrong one. Tickets solved the same problem with per-company lookup maps (getTenantConfigRowsByCompany()); assets would need the same before the list can widen.

Verified

On a real database with throwaway rows (all removed): created into another company from the All companies view; refused a Default-private location for a Gmail asset; moved an asset and saw its private location cleared and audited; moved one on a shared location and saw it kept; refused a hostname clash and an out-of-company holder, naming the person; and a same-company move was a no-op. Then the page was driven headless: the form would not submit without a company, the location list followed the company chosen, and Key info showed the Company field.


See also

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally