Units
One tracked physical item of a Product — with its own location, bin, status, lot, price, and complete movement history. Units are what you check out, sell, transfer, count, and consume.
Product vs. Unit, in one line: a Product is the thing you stock or sell; a Unit is one tracked item of it.
Looking for measurement units like "lb", "gal" or "ea"? That's Units of Measure.
A unit is one physical item of a Product. If the Product is "Cordless Drill, 18V," then each actual drill — sitting in a bin, checked out to a tech, or in transit between warehouses — is a unit. Units carry their own location, bin, status, optional lot and expiration, and a complete movement history, while inheriting their name, price, and usage type from the Product.
Lot numbers, expiry dates and being able to answer "where did this batch go" are what units are for. For the practice — how lot codes are read and what a recall actually takes — see lot traceability and recalls. This page is how units work here.
Naming note: these records are Units in the app and in the REST API alike —
/api/v1/inventory/units, referenced from other resources asunit_id. Don't confuse them with Units of Measure (lb,gal,ea), which are measurement labels served from/api/v1/inventory/units-of-measure.
Everything you do with stock happens to units: you check them out, sell them, transfer them, mark them used, hold them, and count them. The Product just says what they are.
How units come into being #
Units are created several ways:
- Receiving a purchase order — purchasing creates units as stock arrives, landing them in the location's receiving bin.
- Manually — Inventory → Book in Stock, useful for opening balances or stock that didn't come through a PO. You can book in several identical units at once with the quantity to create field. The same form is on the units list header, and its save button says Book in Stock rather than Create — it is a delivery being received, not a record being invented.
- Building from existing inventory — when the Product is composite, creating an unit can consume its component units, which are marked used automatically and linked to the new unit.
- Manufacturing — work orders produce finished units from components.
When you create a unit you choose its Product (required, and it must come first), optional name override, vendor, location and bin, starting status, barcode, and — if the category tracks them — a lot number and expiration date. Pricing can be Each, Total, or Inherit from Product.
Add Stock on a product's page does the same job from the other end — same form, same Book in Stock button — and asks the two questions a delivery answers: which vendor it came from, and the delivery note / packing slip number on the paperwork. When the product's Vendors tab lists exactly one supplier, that supplier is filled in for you. Both end up on every movement the delivery writes, so the Movement Log can say whose goods arrived and against which delivery — which for a business without the purchasing module is its entire receiving audit trail.
Creating a unit through the API is the same
stock-in event and writes the same movement, and POST /api/v1/inventory/units
takes the same facts: vendor_id, reference_number for the delivery note or
packing slip, and an adjustment_reason. Leave bin_id out and the unit lands
in the location's nominated receiving bin, the same shelf the receiving screens
would have offered. The one difference is accounting: stock booked in through
the API is not posted to QuickBooks as a journal
entry, because a script loading stock is not the same act as a person deciding
to adjust it.
Unit statuses #
A unit is always in exactly one status, shown as a badge in the list:
- In Stock — sitting in a bin, available.
- Checked Out — a Returnable unit is out with a person; it has an active checkout record.
- On Hold — reserved for something (an appointment, a sales order, etc.) and not available to others.
- In Transit — moving between locations on a transfer.
- Out of Stock — nothing left: sold, used up, consumed into a build, written off with Discard, or given away with Donate.
- Missing — nobody can find it. The record exists; the thing does not.
Which of these you can set by hand #
Three of them are not yours to set: Checked Out, On Hold and In Transit each belong to another record — a checkout with a borrower and a due date, whatever reserved the unit, a transfer with two ends. Setting one by hand would give the unit a relationship with nothing on the other end of it, so the Status field does not offer them and the app refuses them however they are sent — from the form, the list, the API or an assistant. Use Check Out, reserve the unit from the record that wants it, or put it on a transfer.
Leaving Checked Out or In Transit by hand is refused for the mirror reason: the checkout or the transfer is still open, and only checking the unit in or receiving the transfer closes it. Writing the unit off with Discard or Donate is allowed from either, because that is what happens when a unit is not coming back.
In Stock, Out of Stock and Missing are yours to state outright, individually or with the bulk Change Status action, and the reason you give is recorded on the movement log.
Releasing a hold from the unit itself is deliberately still possible, because the record holding it is not always one you can open — a hold placed by an appointment, on an account without the Appointments module. The Status field warns you what it is about to break, and the release is written to the movement log.
Missing is worth separating from its neighbours, because all the others describe stock whose whereabouts you know. Out of Stock is a unit you consumed, sold, wrote off with Discard or gave away with Donate, and On Hold means somebody reserved it and it is still on the shelf. Missing is the one that says the tool walked out of the building. It is set by answering No — it is missing on a stock count, and a missing unit is dropped out of its bin, counts as zero stock on hand, carries no value on the valuation report, and can never be reserved, allocated, sold, loaned or rented to anybody.
Actions on a unit #
The actions available depend on the Product's usage type and the unit's current status. They live on the unit list and on the unit's own Details page. They are deliberately not on its Edit form: each of them rewrites the status, bin or quantity that form is showing, so running one from there left the fields on screen quietly out of date. Edit is for the unit's own details; Details is where you move it.
- Check Out — Returnable, In Stock only. Hand the unit to a staff user, customer, or contact. A due date is required — it's the day the unit is expected back — and notes are optional. Status becomes Checked Out. See Asset Checkout.
- Check In — Checked Out only. Bring it back into a destination bin; status returns to In Stock and the checkout is closed.
- Mark as Used — Consumable, In Stock only. Record a percentage consumed; the unit's remaining quantity drops by that much, and a unit that hits 0% is emptied out of its bin.
- Transfer — move the unit to a different location and/or bin. See transfers.
- Hold / Release — reserve a unit for an appointment or order, then release it back to stock.
- Discard & Write Off Stock — the unit is gone: damaged, expired, scrapped or spoiled. It goes Out of Stock at zero quantity, leaves its bin, is released from any order or appointment holding it, and can no longer be checked out, sold, rented or counted; its cost comes off the valuation as a write-off. Nothing is deleted — the unit and its history stay on file, and the write-off is added to the movement log with your name and reason against it. Putting the stock back is a deliberate act, not a correction: you set the unit In Stock again, and that is recorded too. Needs the Discard Units permission (see roles and permissions), which a role can be denied while still being able to edit units.
- Donate — give the unit away. The same write-off as Discard with a different reason on the log: the unit goes Out of Stock at zero quantity, leaves its bin, is released from anything holding it, and its cost comes off the valuation. Needs the Discard Units permission. It is on the unit's Details page like every other action here, and also on the units list as a bulk action — donating is rare, and usually a batch.
- Mark as Found — Missing only. It turned up. Say which bin it turned up in and the unit goes straight back to In Stock at full quantity. Losing and finding things is ordinary in a stockroom, so this is a normal move rather than a data repair.
Each of these writes a movement (below), so the history always explains how the unit got to its current state.
Bulk actions #
Select multiple units in the list to Bulk Transfer, Bulk Check Out (eligible units only — ineligible ones are skipped with a count), Discard & Write Off Stock with a reason (every selected unit goes Out of Stock, as above; the action is only offered to a role holding Discard Units), Donate (same permission), or Bulk Change Status — which offers In Stock, Out of Stock and Missing, and leaves any selected unit that is checked out or in transit alone, telling you which and why.
Remaining quantity #
Each unit tracks a remaining quantity as a percentage of one full unit —
handy for partially-used consumables (a half-full drum reads 50%). The form
offers quick-set buttons (100 / 75 / 50 / 25 / 0%) and shows the real amount
alongside, computed from the Product's unit size (e.g. 50% × 500 ml = 250 ml).
Returnable equipment is tracked whole — it's either out or in, not partially
used.
Lots, expiration, location, and bins #
- Lot number and expiration date appear on the unit only when its Product's category is set to track them — keeping the form clean for units that don't need them.
- Location and bin say where the unit physically is. Setting a bin sets the location automatically. In single-location accounts the location field is hidden. Bins are hierarchical, so a bin shows its full path (e.g. Warehouse A → Rack 5 → Shelf 3), with the bin's name in front of it where one has been given (Power Tools Rack A (Warehouse A → Rack 5 → Shelf 3)).
- A bin is required for In Stock units — stock nobody can walk to is not really in stock. Adding stock by hand pre-fills the location's default receiving bin — which every location has, named Receiving, from the moment it is created — and changing the location re-points it at that location's own. So most of the time there is nothing to choose. If the shelf you want does not exist yet, + New Bin on the field creates it at the selected location without leaving the form (subject to the create inventory bins permission). Units that are not in stock (checked out, on order, discarded) need no bin.
- Custom fields inherited from the category appear at the bottom of the form and on the unit's page.
Movement history #
Every meaningful event on a unit is recorded as a movement, visible on the Movements tab of the unit's page. A movement captures the type, the amount and units involved, the source and destination location/bin, the reference (the record it came from — a purchase order, the vendor a by-hand stock-in came from, the checkout a returned unit closes), a delivery note / packing slip number where one was given, and any notes. Movement types include Purchase, Sale, Transfer, Check Out / Check In, Used, Adjustment, Discard, Donation, Return, Material Consumed, and Manufactured. Together they form an unbroken audit trail — nothing changes a unit's location, status, or quantity without leaving a movement behind.
Adjustment reasons #
Most movement types say why the stock moved simply by being what they are: a Purchase came from a vendor, a Sale went to a customer. Adjustment is the exception — it's the type for a change with no document behind it, so on its own it says only that somebody changed the number.
An adjustment can therefore carry a reason:
| Reason | What it means |
|---|---|
| Goods received | A delivery arrived from a supplier and you're booking it in. |
| Found | There's more here than the system said, or something lost has turned up. |
| Missing | There's less here than the system said, and nobody can account for it. |
| Opening balance | Stock you already owned, being entered for the first time. |
| Damaged | The goods are no longer sound and can't be sold or used. |
| Correction | Somebody keyed the wrong number. Nothing physical changed. |
The log then reads Adjustment — Opening balance rather than a bare Adjustment, and you can filter and group by reason to see how much of your shrinkage is loss, damage, or simply mis-keying.
Goods received is there because not every business has the purchasing module. Receiving against a purchase order writes a Purchase movement carrying that order and needs no reason at all — but without that module there are no purchase orders, so every delivery you ever take is booked in by hand, and the reason list has to have a word for it. Picking Found for a delivery would put something untrue in the record.
Reasons are filled in for you where the action already knows the answer: marking a unit missing records Missing, finding it again records Found, and posting a stock count records Found or Missing per line depending on which way the variance went. You're only asked when the system can't know — entering stock by hand, or changing status in bulk — and the reason is always optional.
The Checkouts tab shows the unit's checkout history — who had it, when it left, when it came back, due dates, and overdue flags.
Counting and accuracy #
When you reconcile what's recorded against what's physically on the shelf, the adjust action posts the difference as an Adjustment movement so the remaining quantity matches reality and the history stays honest. See stock counts.
The unit list #
The unit list (Inventory → Units) shows each unit's Product/name, location, bin, status, and remaining %. Filter by category, location, bin, or status, and toggle In stock only to focus on what's actually available.
Doing it from the API #
# List units
curl "https://your-domain.com/api/v1/inventory/units" \
-H "Authorization: Bearer $TOKEN"
# Check a unit back in to a bin
curl -X POST "https://your-domain.com/api/v1/inventory/units/{item}/check-in" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bin_uuid": "..."}'