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.
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).
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.
| Object | What it is | Where 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:
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.
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.
-
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.
-
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."
-
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.)
-
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'smetaJSON, keyed by field key.
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.
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.
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.
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:
asset_checkout permission.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_checkoutpermission ("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.
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 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 chip | Written by | Typical title | Extras 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.
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:
| Data | Asset 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). |
Where to go next
With the mental model in place, dive into the piece that matches your task.
The Browse tab: columns, filters, toolbar, and row actions.
Every field of the add/edit/clone modal, model defaults, and Additional fields.
The full endorsement lifecycle, acceptance agreements, and guards.
Field types, validation, the Fieldsets tab, and the meta JSON.
The four deploy types, the seven seeded labels, and what they gate.
Every term, status vocabulary, and badge colour in one place.