Skip to content

Inventory

The Inventory Aether tracks hardware assets — the categories that shape them, the purchase orders they arrive on, the licences and warranties attached to them, and who currently holds them.

The Assets list with category, status, and assigneepublic/screenshots/inventory-assets.png
  • Asset Categories classify assets and define custom fields that every asset in the category inherits.
  • Purchase Orders record procurement; receiving a PO rolls its line items into stock as assets.
  • Assets are the records themselves — assignable to People (from the Tenancy Aether), with a full assignment history.
  • Software Licences and Warranty Tracker hang off the asset estate.

Classification for assets, plus the custom field definitions every asset in the category inherits — define a field once on the category and it surfaces on every asset beneath it. Each category lists its fields inline; there is no separate field-definition endpoint.

Fields

FieldTypeNotes
namestring · requiredCategory name.
slugstring · uniqueAuto-derived from name when omitted; drives the asset-tag prefix.
iconstringLucide icon key (default package).
descriptionstringFree-form.

Each field definition (assetFieldDefinitions, returned inline as fields) carries:

FieldTypeNotes
categoryIduuid → Asset Category · requiredCascade-deletes with the category.
fieldKeystring · requiredStable key used to store values per asset.
labelstring · requiredDisplay label.
fieldTypestringInput type (default text).
optionsstringJSON string of choices; parsed to an array in responses.
requiredbooleanDefault false.
placeholderstringInput placeholder.
helpTextstringHelper copy.
sortOrderintegerDisplay order (default 0).

APIGET/POST /api/asset-categories, GET/PATCH/DELETE /api/asset-categories/{id}. List and detail responses embed the category’s fields.

Notesslug is unique and feeds a per-category asset-tag prefix (for example laptopLAP). Field definitions have no own CRUD endpoints — they are read back inline on the category; options is stored as a JSON string and parsed to an array in responses. Writes require admin.

An asset category with its custom field definitionspublic/screenshots/inventory-categories.png

Procurement records with line items. Totals recalculate from the line items whenever items change, and receiving a PO rolls its items into stock as asset records. In the web app this is a dedicated list/detail page (not a tab on Assets) with create, edit, receive, and Import PO.

Fields

FieldTypeNotes
poNumberstring · required · uniquePurchase-order number.
supplierNamestringFree-form supplier.
statusenumdraft · ordered · partially_received · received · cancelled (default draft).
orderDatetimestampWhen ordered.
expectedDeliverytimestampExpected arrival.
receivedDatetimestampSet when fully received.
totalValuenumericRecalculated from line items — not set directly.
notesstringFree-form.
createdByuuidProfile that raised the PO.

Each line item (purchaseOrderItems):

FieldTypeNotes
poIduuid → Purchase Order · requiredCascade-deletes with the PO.
descriptionstring · requiredBecomes the asset name on receipt.
categoryIduuid → Asset CategoryCarried onto created assets.
manufacturerIduuid → ManufacturerCarried onto created assets.
quantityOrderedintegerDefault 1.
quantityReceivedintegerIncremented on receive (default 0).
unitCostnumericPer-unit cost.
totalCostnumericunitCost × quantityOrdered.
createsAssetbooleanDefault true. Set false for charges (delivery, freight, tax) — counted on the PO total but no asset is created on receive.
notesstringFree-form.

APIGET/POST /api/purchase-orders, GET/PATCH/DELETE /api/purchase-orders/{id}, plus line items via POST /api/purchase-orders/{id}/items, PATCH/DELETE /api/purchase-orders/{id}/items/{itemId}, and POST /api/purchase-orders/{id}/receive.

Import (web UI) — use Import PO on the Purchase Orders page:

FormatHow it works
CSVOne row per line item; rows sharing po_number group into one PO. Sample downloadable from the modal.
JSONArray of PO objects with nested items. Sample downloadable from the modal.
PDFClient-side text extraction + heuristics. Always opens a review step so you can fix fields and map category/manufacturer before create. Lands as draft only. Scanned/image PDFs (no text layer) need manual entry for now.

CSV columns (aliases accepted): po_number, supplier_name, status, order_date, expected_delivery, notes, description, category, manufacturer, quantity, unit_cost. Category and manufacturer names are matched to existing catalog records when possible. Unmatched categories can be created inline (+ Create category) during PDF review. Lines matching fee keywords (delivery, freight, GST, …) are flagged as charges (createsAsset: false) so they do not spawn assets when received.

NotestotalValue and each item’s totalCost recalculate from unitCost × quantity; you don’t set the header total directly. Creating a PO requires at least one line item. Receiving validates each unit against the line’s remaining quantity, creates one asset per unit received (inheriting the item’s description, categoryId, and manufacturerId), bumps quantityReceived, and moves the PO to partially_received or received — a PO can only be received from ordered or partially_received. A PO that has received items cannot be deleted. Access is role-gated (procurement / sysadmin / it_manager / administrator / super_admin can write; auditor / read_only are read-only).

A purchase order with line items and a receive actionpublic/screenshots/inventory-purchase-order.png

The asset records — make, model, serial, status — assignable to people with a full assignment history, and carrying any custom field values inherited from their category.

Fields

FieldTypeNotes
namestring · requiredDisplay name.
assetTagstring · uniqueAuto-generated from the category prefix when omitted.
serialNumberstringManufacturer serial.
statusenumactive · spare · maintenance · retired · decommissioned · lost · stolen (default active).
conditionstringDefault good.
categoryIduuid → Asset CategoryDrives inherited custom fields and the tag prefix.
manufacturerIduuid → ManufacturerMaker.
siteIduuid → SiteCurrent site.
locationIduuid → LocationCurrent location.
purchaseOrderIduuid → Purchase OrderSet when received from a PO.
purchasedAttimestampPurchase date.
warrantyUntiltimestampInline warranty expiry.
purchasePricenumericAcquisition cost.
notesstringFree-form.

Custom field values are stored per asset in assetFieldValues (assetId → Asset, fieldKey, value) and submitted as a fieldValues map on create/update.

APIGET/POST /api/assets, GET/PATCH/DELETE /api/assets/{id}, plus POST /api/assets/{id}/assign, POST /api/assets/{id}/return, and GET /api/assets/{id}/assignments.

NotesassetTag is unique and auto-generated when omitted, using a per-category prefix (for example LAP-000001) with collision retry. Custom fields are per-category: definitions live on the category, values are stored per asset and submitted as a fieldValues map. Assigning closes any open assignment before opening a new one; assign/return target a person (Tenancy Aether) or profile, and GET /api/assets/{id}/assignments returns the full history. The list endpoint also accepts category, status, and assigned filters. Deleting an asset requires admin.

Software licence records — seats, keys, expiry, and the vendor that supplies them. Seat usage is tracked but not enforced.

Fields

FieldTypeNotes
namestring · requiredLicence name.
productstringProduct covered.
licenceKeystringKey or serial.
seatsintegerTotal seats.
assignedSeatsintegerSeats in use (default 0).
vendorIduuid → VendorSupplier; set null on vendor delete.
expiryDatedateRenewal date.
costnumericLicence cost.
statusstringDefault active.
notesstringFree-form.

APIGET/POST /api/software-licences, GET/PATCH/DELETE /api/software-licences/{id}.

Notesseats and assignedSeats are recorded for visibility; assignedSeats is a plain count, not derived from asset assignments, and is not enforced against seats.

The Software Licences list with seats and expirypublic/screenshots/inventory-licences.png

Warranty records against assets — coverage dates and provider, so expiries are visible before they bite.

Fields

FieldTypeNotes
namestring · requiredWarranty name.
assetIduuid → AssetCovered asset; cascade-deletes with the asset.
providerstringWarranty provider.
typestringCoverage type.
startDatedateCoverage start.
endDatedateCoverage end.
costnumericWarranty cost.
statusstringDefault active.
notesstringFree-form.

APIGET/POST /api/warranties, GET/PATCH/DELETE /api/warranties/{id}.

NotesassetId is optional but cascade-deletes with its asset, so warranties vanish when the covered asset is removed. startDate/endDate are plain dates; an asset also carries its own inline warrantyUntil, separate from this tracker.

Resources are exposed under /api/assets, /api/asset-categories, /api/purchase-orders, /api/software-licences, and /api/warranties — see the API reference.