Skip to content

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.

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)

See Derived state: stock per article and location, serial number states, the booking history (newest first by booked_at, then hlc).

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

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.

schemas/movement.json
{
"$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"
}
]
}