movement
A movement is one stock booking. Stock and serial number states are derived from movements and nothing else.
- Immutable: the first version a node receives is kept forever. Later upserts with the same id are ignored, deletes are rejected with
immutable. A wrong booking is corrected with a counter-booking (an issue against a wrong receipt, or a stock count). - Ids: random UUIDs, one per booking. Never reuse an id for a different booking.
- Servers MUST NOT reject a movement because of the stock it leads to. It has already happened on a device.
type |
from_location_id |
to_location_id |
quantity |
Effect |
|---|---|---|---|---|
receipt |
required | positive | adds at to |
|
issue |
required | positive | subtracts at from |
|
transfer |
required | required | positive | subtracts at from, adds at to |
adjustment |
required | signed difference | adds quantity at to |
"Required" in this table is a rule for writers; the reference server does not check which location fields a type has, and readers ignore a side whose location is missing.
Fields
Section titled “Fields”| Field | Type | Writer | Default | Meaning |
|---|---|---|---|---|
type |
string | server | receipt, issue, transfer or adjustment. Readers skip movements of other types. |
|
article_id |
uuid | server | The article booked | |
from_location_id |
uuid? | see table | none | Where the quantity leaves |
to_location_id |
uuid? | see table | none | Where the quantity arrives or was counted |
quantity |
number | server | In the article's unit. Positive, except for adjustment, where it is the counted stock minus the stock the device computed before the count |
|
counted |
number? | optional | none | adjustment only: the counted stock. Informational; stock follows quantity |
serials |
array of string? | optional | [] |
Articles with track_serials: one serial number per unit, so the count equals quantity (not for adjustment) |
note |
string? | optional | none | Free text. The reference app writes the document number here for bookings of delivery notes and loans |
delivery_note_id |
uuid? | optional | none | The delivery note that booked this issue |
loan_id |
uuid? | optional | none | The loan that booked this movement: issue at handover, receipt at return |
booked_at |
date-time? | optional | sorts first | When the booking happened on the device. Orders the history and the serial states |
booked_by |
string? | optional | none | Display name of the person who booked (the name set in the app) |
Derived values
Section titled “Derived values”See Derived state: stock per article and location, serial number states, the booking history (newest first by booked_at, then hlc).
Server validation
Section titled “Server validation”| Field | Rules |
|---|---|
type |
required, one of receipt, issue, transfer, adjustment |
article_id |
required, UUID |
quantity |
required, numeric |
from_location_id, to_location_id, delivery_note_id, loan_id |
nullable, UUID |
counted |
nullable, numeric |
serials |
nullable, list; each element a string |
note, booked_by |
nullable, string |
booked_at |
nullable, date |
Examples
Section titled “Examples”Receipt of two serial-tracked drills:
{ "entity": "movement", "id": "77AA0C41-2E9B-4F7D-8B1A-5D3C2E1F0A99", "op": "upsert", "hlc": "001791792000123-0002-3F2A9C1B", "data": { "type": "receipt", "article_id": "4A7C2E91-0B3D-4F6A-8E15-7C9B2D4F6A80", "to_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "quantity": 2, "serials": ["DR-1001", "DR-1002"], "note": "Order 4711", "booked_at": "2026-10-12T08:00:00Z", "booked_by": "Erika Mustermann" }}Stock count: 17 counted where the device computed 20.
{ "entity": "movement", "id": "3E5A7C9B-1D2F-4A6B-8C0E-2F4A6C8E0B13", "op": "upsert", "hlc": "001791797400000-0000-3F2A9C1B", "data": { "type": "adjustment", "article_id": "8C1F3D2E-5B7A-4A61-9F0E-2B4C6D8E0A13", "to_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "quantity": -3, "counted": 17, "booked_at": "2026-10-12T09:30:00Z", "booked_by": "Erika Mustermann" }}More examples, one per workflow, are on the stock bookings page.
Schema
Section titled “Schema”{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Movement", "description": "One stock booking. Immutable: the first write wins, deletes are rejected, a wrong booking is corrected with a counter-booking. Effect on stock: receipt adds quantity at to_location_id, issue subtracts it at from_location_id, transfer moves it from from_location_id to to_location_id, adjustment adds quantity (which may be negative) at to_location_id.", "type": "object", "required": ["type", "article_id", "quantity"], "$defs": { "uuid": { "type": "string", "format": "uuid", "pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$" }, "optionalUuid": { "anyOf": [{ "$ref": "#/$defs/uuid" }, { "type": "null" }] } }, "properties": { "type": { "type": "string", "enum": ["receipt", "issue", "transfer", "adjustment"] }, "article_id": { "description": "The article booked.", "$ref": "#/$defs/uuid" }, "from_location_id": { "description": "Issue and transfer: where the quantity leaves.", "$ref": "#/$defs/optionalUuid" }, "to_location_id": { "description": "Receipt, transfer and adjustment: where the quantity arrives or is counted.", "$ref": "#/$defs/optionalUuid" }, "quantity": { "description": "Positive amount in the article's unit; for adjustment the signed difference between the counted and the previous stock.", "type": "number" }, "counted": { "description": "Adjustment only: the counted stock at to_location_id. Informational; stock follows quantity.", "type": ["number", "null"] }, "serials": { "description": "For articles with track_serials: one serial number per unit. Compared case-insensitively. Absent or null means an empty list.", "type": ["array", "null"], "items": { "type": "string" }, "default": [] }, "note": { "description": "Free text. Bookings made by a delivery note or loan carry its number here.", "type": ["string", "null"] }, "delivery_note_id": { "description": "The delivery note that booked this issue.", "$ref": "#/$defs/optionalUuid" }, "loan_id": { "description": "The loan that booked this movement: issue at handover, receipt at return.", "$ref": "#/$defs/optionalUuid" }, "booked_at": { "description": "When it happened on the device. Orders the history and serial number states; stock does not depend on it. The reference app sorts a movement without it first.", "type": ["string", "null"], "format": "date-time" }, "booked_by": { "description": "Display name of the person who booked.", "type": ["string", "null"] } }, "additionalProperties": true, "examples": [ { "type": "receipt", "article_id": "4A7C2E91-0B3D-4F6A-8E15-7C9B2D4F6A80", "to_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "quantity": 2, "serials": ["DR-1001", "DR-1002"], "note": "Order 4711", "booked_at": "2026-10-12T08:00:00Z", "booked_by": "Erika Mustermann" }, { "type": "adjustment", "article_id": "8C1F3D2E-5B7A-4A61-9F0E-2B4C6D8E0A13", "to_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "quantity": -3, "counted": 17, "booked_at": "2026-10-12T09:30:00Z", "booked_by": "Erika Mustermann" } ]}