Asset Tag Numbering Developer Guide - edmozley/freeitsm GitHub Wiki
Asset tag numbering โ Developer Guide
Since 3.1.0 ยท Built by Sandy (@srinivasansanthosh) in PR #164, merged with the changes in the review ยท User side: QR asset labels
How FreeITSM gives a new asset the next tag in sequence (AST-00042), and how every write of a tag โ typed, generated or edited โ is kept unique within a company.
1. ๐ The files
| File | Role |
|---|---|
includes/services/asset_tags.php |
AssetTagsService โ settings, validateFormat(), preview(), generate(), createWithTag(), assign(), withLock() |
api/assets/get_asset_tag_settings.php, save_asset_tag_settings.php |
The settings tab's read and write (Cap::ASSETS_TAGS) |
api/assets/asset_tag_preview.php |
Live preview โ writes nothing |
api/assets/save_asset_tag.php |
The tag box on an asset โ now AssetTagsService::assign() |
asset_tag_counters |
The counters (counter_key PK, registered in db_verify.php's $primaryKeys) |
tests/asset-tag-numbering.php |
29 checks |
2. ๐งฉ The shape is ticket numbering's, on purpose
3.0.0 shipped ticket numbering you choose the shape of. Asset tags use the same model rather than a second one:
| Setting | Notes | |
|---|---|---|
| On/off | asset_tag_autogen_enabled |
Per company. Off by default โ an upgrade changes nothing. |
| Format | asset_tag_format |
Per company. Default AST-{#####}. Rendered by TicketNumbering::render() itself, so the tokens and their rules are identical: {###} is a minimum width, never a limit; {COMPANY} is the company's ticket code. |
| Start | asset_tag_start |
Per company. Used the first time a counter is claimed. |
| Scope | asset_tag_scope |
Install-wide. per_company (default โ tags only have to be unique within a company) or global. |
Sandy's original had five settings โ prefix, suffix, padding, initial number, enabled. A format string is those in one line, and the day somebody asks for {COMPANY}-LT-{####} it simply works, with no new settings and no migration.
validateFormat() allows exactly one number token, {YYYY} {YY} {MM} {DD} {COMPANY}, and letters, digits and - _ . /. {TYPE} is refused โ it's a ticket type. The 64-character column limit is checked against a rendered six-digit number with a long stand-in company code.
3. ๐ข The counter
INSERT INTO asset_tag_counters (counter_key, next_value) VALUES (?, ?)
ON DUPLICATE KEY UPDATE next_value = LAST_INSERT_ID(next_value + 1)
The same statement, and the same rowCount() trap, as TicketNumbering::claimNext() โ read that method's comment. next_value holds the last number issued. Keys: asset:co<id> per company (0 = Default, so never NULL), or asset for global.
- Uniqueness is proven, not assumed. After claiming,
generate()checks the tag against the company's assets; if it is taken (a restored backup, tags typed by hand) it winds the counter forward with a doubling stride โ but the first retry lands on the very next number ($seq + $step - 1), because stickers are printed in runs and a needless gap reads as a missing asset. Ticket numbering skips one there; tags don't. - Forward only.
setNextNumber()and every wind useGREATEST(). A number on a sticker is never handed out again. - Rolls back with a failed asset.
createWithTag()claims the number inside the transaction that inserts the asset, so a failed insert gives it back. If the caller already has a transaction, it joins it and neither commits nor rolls back itself. (Sandy's requirement, kept.)
4. ๐ One lock for every write of a tag
Per-company uniqueness lives in code (assetTagAvailable()), because a UNIQUE (tenant_id, asset_tag) index cannot hold for the Default company โ see the NULL trap. A check-then-write must be serialised, and every path that writes a tag goes through AssetTagsService::withLock() โ a MySQL named lock, freeitsm_asset_tag_co<id>, which fails closed:
| Path | Method |
|---|---|
| A tag typed on create (screen, REST, import) | createAsset() โ createWithTag() |
| A generated tag (any creator, agents and Intune included) | createWithTag() โ generate() |
| A tag changed on an existing asset | assign() (save_asset_tag.php) |
Two paths with two locks โ as the branch first had it, a lock for typed tags only โ would let a typed AST-00005 and a generated AST-00005 both through.
noteManualTag(): a typed tag with the generated shape and a number ahead of the counter winds the counter past it, so the generator never has to skip it later.
A clash names the asset that has the tag (clashMessage()): "That tag is already on LAPTOP-12."
5. ๐งช Testing it
AssetTagsService::withSettings([...]) / forget() is the house override (TicketNumbering's pattern), so a test never writes the install's real settings. php tests/asset-tag-numbering.php โ 29 checks: formats and preview; a failed create returns its number (the one check run with the service owning its transaction, failing before anything is written); consecutive tags; a taken tag skipped without a gap; typed tags kept, winding the counter, refused on a clash with the other asset named; assign() with history; forward-only; scope keys; off means no tag; and an agent-found asset tagged too. Everything else is in one rolled-back transaction, rows prefixed ZZTAG-.
6. Not built (and why)
- Bulk renumbering โ ticket numbering has it as a migration tool; asset tags are on physical stickers, so renumbering means re-labelling. Wait until somebody needs it.
- Immutable tags โ the QR token is the asset's permanent physical identity, so a tag can change (with history) and every printed label still scans.
- Per-type or per-location rules โ the format string leaves room for tokens; none are needed yet.
See also: Asset reconciliation โ Developer Guide ยท QR asset labels โ Developer Guide ยท The review (PR #164) ยท Developer Tests โ Assets