Receiving and consuming inventory

Receiving, staging and checkout sessions, batch placement, and how stock is drawn down.

Editing one vial is fine to do by hand. Past ten, batch the work. Dalea batches inventory in three shapes, and they are not interchangeable:

Receiving session
The only accumulate-then-commit flow: scan rows into an open session over time, then commit it as one act. Intake only. Staging and checkout "sessions" are not batches, they are one row per item, written and removed immediately.
Batch calls
Batch place and batch check-out. One request, up to 500 rows, no session to keep track of.
Inventory Operation
A document block that previews a multi-row change and commits it atomically. This is the only batched drawdown path.

There is no consumption session. Quantity drawdown is per item, or a consume Inventory Operation, and both are covered below.

Receiving sessions are API-only

The receiving-session endpoints are real and supported, but no screen in the app drives them, and the public SDK does not expose them either. Use the REST API. The in-app bulk-intake surface is Inventory → Settings → Import / Export, which takes a long-format CSV (one row per field value) with the headers Container Path, Container Name, Container Barcode, Position, Object Display ID, Container Type, Item Type, Field Name, Field Type, Field Value.

Receiving a shipment

Sigma delivers 10 vials of an anti-IFN-γ capture antibody, lot 24-119.

  1. Create the session

    POST /inventory/receiving-sessions with the itemTypeId of the inventory table the vials belong to. A session covers exactly one item type.

  2. Set the lot

    PATCH /inventory/receiving-sessions/{id} with either lotMode: "new" plus lotDetails (lotNumber, and optionally description, expirationDate, receivedDate, notes), or lotMode: "existing" plus lotId. The commit refuses to run until the lot is decided, and every item inherits it.

  3. Scan the barcodes in

    POST /inventory/receiving-sessions/{id}/scan with { raw }, plus quantity and unit when the barcode does not carry them. GS1 barcodes are parsed, so lot number and expiry are picked up automatically; the response tells you whether the scan was a duplicate and whether its lot number disagrees with the session's. Correct or drop a scanned row with PATCH or DELETE on /inventory/receiving-sessions/{id}/items/{itemId}.

  4. Commit

    POST /inventory/receiving-sessions/{id}/commit takes no body. It creates the lot if it is new, then the items, and returns lotId, lotNumber, itemCount, itemIds and a per-row errors array. There is no audit reason on a receiving commit; free-text context belongs in the session's metadata or in lotDetails.notes.

  5. Place them

    A receiving session has no destination, so the 10 vials land unplaced and read available. Put them away with POST /inventory/batch-place, or from the container grid in the app.

A receiving commit is all or nothing

Every row in a session shares one item type, and object creation is atomic per item type. If one row fails validation, nothing is created: the offending row comes back with the failing column and message, and the innocent rows come back saying another item of the same type failed. Fix the bad row and commit the session again. There is no double-stocking risk, because the first attempt created no items.

Placing and moving in bulk

Reorganising a freezer is not a session, it is a batch call.

POST /inventory/batch-place
placements: [{ containerId, position, itemId }], up to 500. An available or staged item is placed; a checked-out one is returned. A position holds one item.
POST /inventory/batch-checkout
placements: [{ placementId }], up to 500. Only a currently placed item can be checked out, and its placement row is removed.
POST /inventory/placements/{id}/move
Move one placed item to another container or position.

Unlike a receiving commit, both batch calls are partial-success: each row runs in its own transaction and the response carries a count plus an errors array naming the rows that failed, by index.

Staging is the parking space for items with no home yet: POST /inventory/staging-sessions with itemIds stages them, DELETE on the same path unstages. A staged item can only be placed by the person who staged it.

Consuming stock

Drawdown is per item, and it has two operations that behave differently:

Log Usage
POST /inventory/items/{id}/consume. Decrements the quantity and records a consumption event. Requires the item to be checked out by you, and requires an audit reason.
Adjust
POST /inventory/items/{id}/adjust-quantity. Sets a new absolute quantity. No custody precondition, so it works on a placed item, and a reason is mandatory.

Log Usage is the honest record of material leaving; Adjust is the correction when the number on the screen and the number in the tube disagree.

For many items at once, insert an Inventory Operation block in a document (/Inventory Operation). It previews the change server-side and then commits up to 500 rows atomically, and it covers place, checkout, return, move, consume, adjust and create_container. A consume row is subject to the same rule: each item must already be checked out by you.

An item drawn to zero becomes depleted, and an item at or below its threshold becomes low_stock. Neither is a custody change: the item keeps whatever custody state it had, and taking it out of circulation is the separate Discard action, which archives it.

Tracing a reagent to a result

Each quantity write is recorded in the provenance graph the same way any other object edit is, so an item carries an attributable drawdown history: who, when, how much, and why. To ask "which results used lot 24-119", query the stock table on its _lot_number or _lot_id reserved field and join it to the records that reference those items. There is no direct link field from a consumption event to a result batch or a document.

Low stock

The quantity column of an item type can carry a low-stock threshold. When a drawdown pushes an item to or below it, that item's quantity state becomes low_stock, and an item drawn down to zero becomes depleted.

This is a state, not an alert. Nobody is notified. To run a reorder list, filter on it: a saved query over the stock table with _quantity_state in low_stock, depleted gives you exactly what needs buying, grouped by whatever you order against.

What's next