Assets Reference Public

Core Concepts & Data Flow

Introduction Core Concepts & Data Flow The mental model behind Safekeep: one asset record at the center, a small catalog of master data that shapes it, an endorsement lifecycle…

Guide version: r1 Module version: 1.0.0 Updated: 2026-07-22 Estimated time: 14 min 1 views
Introduction

Core Concepts & Data Flow

The mental model behind Safekeep: one asset record at the center, a small catalog of master data that shapes it, an endorsement lifecycle that moves it between people, and a chain-of-custody log that remembers everything. Learn this once and every feature page will make sense.

ℹ️
This page is conceptual. It explains how the pieces fit together and where data flows — it does not list every field. Field-by-field details live on each feature page (Adding & Editing Assets, Checkout & Check-in, Asset Models), and every term, status, and badge colour is defined in the Glossary & Legends.

The asset record — the hub of everything

Every physical thing you track — a laptop, a phone, a vehicle, an access card — is one row in the asset_entries table, called an Asset Entry in the UI (the add button on the Browse tab is literally "Add entry"). Everything else in the module either describes that record (categories, models, status labels, locations, custom fields) or happens to it (checkout, acknowledgement, return, audit, maintenance, comments).

An asset record carries three groups of data:

Identity & description
Title, Description, Asset Tag (the printed label ID, e.g. AST-000123), Serial no., Photo, plus classification pointers: Category, Asset Model, Brand, Vendor, Type (Own / Lease / Rental / Contract / Service), Location, and Labels.
Money & time
Cost, Purchase date, Order Number, Warranty expiry date, Warranty (months), Condition, Last Audit / Next Audit dates, and a Depreciation profile inherited through the Asset Model.
Custody state
Who it is endorsed to ("Issued to"), when it went out ("Issued at"), when it is due back ("Due / return by"), whether the assignee has confirmed receipt, and the current endorsement status (see the lifecycle below).
????
One more column matters conceptually: meta — a JSON blob on the asset row that stores the values of the asset's custom fields (the "Additional fields" section of the form). No table columns are ever added per field; every extra value lives in this JSON, keyed by the field's stable field key. That design is deliberate and multi-tenant-safe. Details below and on Custom Fields & Fieldsets.

The catalog around the asset

Seven kinds of master data shape what an asset record looks like and what may happen to it. Each links to its own feature page.

ObjectWhat it isWhere it lives
Category
categories.html
What kind of thing the asset is (Laptop, Phone, Vehicle, Access card, …). Categories form a hierarchy (nesting is capped — a save deeper than 3 parent layers is rejected with "Maximum of 3 layers parenting only"). Each category can bind one Fieldset, which decides the custom fields shown for assets in that category. Category is the only built-in dropdown that is required on the asset form. Safekeep → Categories page. Table asset_categories. Six categories are seeded on install.
Fieldset
custom-fields.html
A named, ordered bundle of custom fields, with a per-fieldset "Required" flag on each row. A category binds one fieldset; the system compiles it into a cached JSON schema on the category so the asset form (and the mobile app) can render the fields instantly. Custom Fields page → "Fieldsets" tab. Tables asset_fieldsets + asset_fieldset_fields.
Custom Field
custom-fields.html
A reusable extra field (e.g. "Serial number", "IMEI", "Plate number") with a stable Field Key, one of 8 input types (Text, Textarea, Number, Date, Dropdown (select), Checkbox, Radio, Yes / No (boolean)), a validation format, and optional flags such as "Primary (identifying) field". Fields never attach to an asset directly — only through a fieldset. Custom Fields page (ams/custom_fields). Table asset_custom_fields.
Asset Model
models.html
A catalog entry for a hardware model (e.g. "MacBook Pro 14 M3"). It ties together a Category, a Manufacturer, a Fieldset, a Depreciation profile, an EOL horizon in months, and a Requestable flag — and pre-fills new assets (see the flow below). Assets page → "Models" tab. Table asset_models. None are seeded.
Status Label
status-labels.html
The asset's named, coloured state ("Ready to Deploy", "In Repair", …), each classified into one of four deploy types that gate checkout — and nothing else. See below. Categories page → "Status Labels" tab. Table asset_status_labels. Seven labels are seeded.
Location
locations.html
A physical place, in the same capped hierarchy as categories (e.g. HQ > 3F > Server room). Used on asset records and on Asset Groups. No fieldset binding, no behavior — pure classification. Assets page → "Location" tab. Table asset_locations. None are seeded.
Asset Group
groups.html
A named bundle of asset entries (a workstation kit, a room's equipment) pinned to a location and taggable with labels. Membership is a simple list of asset IDs stored on the group itself — grouping an asset changes nothing on the asset record. Categories page → "Groups" tab. Table asset_groups.

Shared masters: Manufacturers & Suppliers

Two dropdowns on asset and model forms are not owned by this module:

????
Manufacturer = shared Brands

The Model form's "Manufacturer" and the asset form's "Brand" both read the core Brands catalog (brands flagged for use in assets). Manage them on the shared Brands screen at ams/brands — there is no manufacturer screen inside Safekeep.

????
Supplier = shared Vendors

The asset form's "Vendor" and the Maintenance screen's supplier dropdown read the core Vendors table, reachable at ams/vendors. Vendor permissions also stay in the core Roles screen.


How custom fields reach an asset

The single most useful chain to memorise. Custom fields are global masters; a fieldset bundles and orders them; a category binds one fieldset; the asset form renders the result as the "Additional fields" section whenever that category is selected.

Custom Fields
global reusable field masters
Fieldset
ordered, required-aware bundle
Category
binds one fieldset; caches the schema
Asset form
"Additional fields" → meta JSON
  1. Create Custom Fields

    Define each field once on the Custom Fields page — name, field key, type, format, options, help text. Fields are deduplicated by key, so "Serial number" can be shared by the Laptop and Monitor fieldsets without duplication.

  2. Bundle them into a Fieldset

    On the Fieldsets tab, pick fields row by row — row order sets display order, and each row has its own "Required" checkbox. The help text on the builder says it plainly: "Bind custom fields to this fieldset; row order sets the display order. Categories using this fieldset are recompiled automatically."

  3. Bind the Fieldset to a Category

    The Category modal has a "Fieldset" dropdown. On save, the system immediately compiles the fieldset into a cached JSON schema on the category, so the asset form and the mobile API pick up the fields at once. (An Asset Model can also record a fieldset reference in its catalog entry, but only the Category's binding drives the asset form's fields.)

  4. Fill the fields on the asset

    When you pick that category on the asset form, the "Additional fields" section re-renders with the category's fields. Required fields show a trailing * and are enforced server-side ("%s is required."; a non-numeric value in a Number field gets "%s must be a number."). Values are saved into the asset's meta JSON, keyed by field key.

⚠️
Where do the values show up? Custom-field values are visible in the Edit modal and in the XLSX export (each field becomes an export column) — the asset detail page does not display them. See the "Where do I see what" table below.

How a Model pre-fills a new asset

The Asset Model is the other feeder into the asset form. It is a convenience and a policy carrier, not an enforcement mechanism.

Asset Model
category · manufacturer · fieldset · depreciation · EOL
Defaults
auto-fill Category + Brand if still blank
Asset
inherits depreciation via the model

Concretely: picking an "Asset Model" on the asset form auto-fills Category and Brand — but only if you have not already chosen them (the server applies the same defaulting on save). The model's Depreciation profile and EOL months are not copied onto the asset; the asset inherits them through the model, which is what the monthly depreciation computation and the Depreciation schedule report read. See Asset Models and Depreciations.


Status Labels and the four deploy types

Every status label is classified into one of four deploy types (the Status Labels form calls the picker "Field Type"). The type is what the system actually reads — the label name and colour are for humans.

Deployable
Can be checked outThe only type that allows checkout. Seeded: Ready to Deploy, Deployed.
Pending
Waiting on somethingNot deployable. Seeded: Pending, In Repair.
Undeployable
Out of serviceNot deployable. Seeded: Broken, Lost / Stolen.
Archived
Out of circulationNot deployable. Seeded: Archived.

Seven labels are seeded on install — Ready to Deploy (marked as the "Default" label, brand teal), Deployed, Pending, In Repair, Broken, Lost / Stolen, and Archived. You can rename, recolour, or add your own on the Categories page's Status Labels tab.

Statuses gate exactly one thing: checkout. If an asset's current status label is not of type Deployable, the checkout is rejected with "This asset's status is not deployable, so it cannot be checked out." That is the whole rule. Changing a status never auto-checks-in, never reassigns, and checkout never changes the status (it does not auto-set "Deployed" — you set statuses manually on the edit form or via the check-in modal's "Set status to" dropdown). Also note: an asset with no status at all can be checked out without any status guard, and checkout targets are staff users only — not locations or other assets.

The endorsement lifecycle

Safekeep's word for custody is endorsement: an asset is endorsed (checked out) to a staff user, who confirms receipt, may ask for it to be taken back, and eventually returns it. The asset carries a single endorsement status that moves through four states:

Assigned
"Check out asset" — endorsed to a user
Acknowledged
"Confirm receipt" by the assignee
Return requested
"Request return" — assignee still holds it
Returned
"Check in asset" — assignment cleared
AssignedChecked out. Sets the assignee, issue date, and optional due date. Needs the asset_checkout permission.
AcknowledgedThe assignee (or an admin) confirmed receipt — with EULA acceptance and a typed signature when the category requires it.
Return requestedA flag, not a transfer — the assignee keeps the asset until someone checks it in.
ReturnedChecked back in. Assignment, due date, and acknowledgement are cleared; condition and status can be updated on the way in.

Three rules worth internalising:

  • "Checked out" means: the asset has an assignee and its endorsement status is not "Returned". Check-in is allowed from any of the three active states — acknowledging and requesting a return are optional stops, not prerequisites.
  • Who does what: checkout and check-in need the asset_checkout permission ("Assets: Checkout / Endorse"); Confirm receipt and Request return have no permission key — they belong to the current assignee (or an admin). See Permissions & Roles.
  • Acceptance agreements are category-driven: if the asset's category requires acceptance, the Confirm-receipt modal requires a "Type your full name to sign" field — and, when the category also has agreement text, an "Acceptance agreement" box with a required "I have read and accept the terms above." checkbox. Full walk-through on Checkout & Check-in.
⚠️
No admin UI for acceptance agreements. The category flags that switch the EULA flow on (require_acceptance and eula_text) are not exposed anywhere in the Categories screen — they can only be set directly in the database today. Unless a database admin has done that, "Confirm receipt" is a plain confirmation with no agreement text. See Categories.
⚠️
The edit-form back door. Setting "Issued to" on the Edit modal reassigns the asset without going through the lifecycle: no deployability check, no endorsement status change, and no event in the Activity timeline (only a comment-style change entry). For a clean chain of custody, always use "Check out asset" / "Check in asset".

The chain-of-custody events log

Every lifecycle action writes exactly one row into the events log (asset_events). This ledger is the module's spine — it backs the "Activity" timeline on the asset detail page (newest first; empty state: "No activity recorded yet.") and the Activity log report. Each event records what happened, to whom, when, who did it, and an optional note.

Timeline chipWritten byTypical titleExtras recorded
Endorsed "Check out asset" "Checked out to %s" Target user = the assignee; the checkout note as the body.
Acknowledged "Confirm receipt" "Receipt confirmed." Acceptance timestamp; when acceptance was required, a frozen copy of the EULA text as accepted and the typed signature — the record survives even if the category's EULA changes later.
Return requested "Request return" "Return requested" Optional note.
Returned "Check in asset" "Checked in from %s" Target user = the previous assignee; the check-in note.
Audit "Record audit" (Auditing Assets) "Audited on %s" Condition note prepended when a condition was entered.
Maintenance Logging a new maintenance record (Maintenance) The maintenance record's title One event when the record is first created — editing it later does not add another.

Ten event kinds have display labels in total, but only the six above are ever written by the shipped screens. The other four — "Checkout", "Check-in", "Note", "Issue" — are reserved vocabulary with labels prepared and no writer yet. On the detail page, every chip renders in the same blue regardless of kind.

ℹ️
Two known gaps in the chain. (1) Assignments created during CSV import ("checkout-on-import") set the assignee and the Assigned status but log no Endorsed event. (2) Reassigning via the Edit form's "Issued to" bypasses events entirely (see above). Everything done through the Actions menu on the detail page is fully logged.

Comments are a parallel track

Below the Activity timeline, each asset has a comment thread (permission asset_comment): rich-text comments with @-mentions and file attachments. Two things land here automatically: the asset's Description is pinned as the first, undeletable comment, and most edit-form changes write a pinned change entry (e.g. "Issued to updated from A to B" — changes to the newer lifecycle fields such as Status or Asset Tag are not change-logged). Comments can be deleted but never edited (permission delete_asset_comment; pinned entries cannot be deleted at all). Sort order and who may be mentioned are set in Settings → Asset Entries Configuration — see Widgets, Reminders & Settings.


Where do I see what

The most common source of confusion: the asset detail page is a custody dashboard, not a full data sheet. It shows the header and sub-title strip (title, tag, category, vendor, type, brand, model, serial number, location), the 4-column lifecycle summary (Status · Endorsed to · Due / return by · Next Audit), the Activity timeline, and the comment thread — only those. Everything else lives elsewhere:

DataAsset detail page?Where you actually see it
Title, asset tag, category, brand, model, serial no., vendor, type, location ✔ header / sub-title strip All but the asset tag are also columns on the Asset Registry list.
Status label, assignee + endorsement status, due date, next audit ✔ lifecycle summary panel Also the dashboard widgets ("Assets assigned to me", "Assets needing attention").
Chain-of-custody events ✔ "Activity" timeline Company-wide view: the Activity log report.
Comments, description, change log ✔ comment thread
Photo Edit modal (paperclip link) and the "Image" column of the XLSX export.
Cost, purchase date, warranty dates, order number Edit modal and XLSX export; cost / purchase / warranty are also list columns on the Asset Registry.
Condition Edit modal; updated via the check-in and audit modals.
Custom-field ("Additional fields") values Edit modal and the per-field columns of the XLSX export.
Maintenance history ✘ (only the single "Maintenance" event chips in Activity) The Maintenance screen and the Maintenance log report.
Book value / depreciation The Depreciation schedule report (recomputed monthly — see Depreciations).
????
Rule of thumb: custody questions → detail page; data questions → Edit modal or export; history questions → Activity, Maintenance, or a report.

Where to go next

With the mental model in place, dive into the piece that matches your task.

Was this guide helpful?

Report a content problem