models:
- name: StockMove
collection: stock_moves
description: "A stock move — one item x (source -> destination) demand with the 7-state lifecycle (draft/waiting/confirmed/partially_available/assigned/done/cancel); the owner of the transfer's projected state (T1)"
fields:
id:
type: uuid
attributes: ["@id", "@default(uuid)"]
description: "Unique move identifier"
name:
type: string
attributes: ["@required", "@max(400)"]
description: "Move description"
state:
type: MoveState
attributes: ["@default(draft)"]
lifecycle:
shape: hand_set
sticky: false
description: "7-state lifecycle (snake_case values): draft / waiting / confirmed / partially_available / assigned / done / cancel. Driven only by the guarded state machine declared in schema/hooks/stock.hook.yaml — never free-hand-set over HTTP"
posting_state:
type: GlPostingState
attributes: ["@default(not_applicable)"]
description: "GL settlement state of the move's OWN AccountingPost leg (the envelope _action_done builds when the caller's directive supplies accounts): not_applicable while no envelope was built (voucher-door moves whose GL is owned by the voucher, value-neutral shapes, missing directive accounts, or not yet done), pending from the done write until the ack/rejection is recorded, then posted | failed. failed = the physical movement stands but its GL leg is missing — repost_move_gl re-drives it"
priority:
type: Priority
attributes: ["@default(normal)"]
description: "normal / urgent (raises scheduler weight)"
create_date:
type: datetime
attributes: ["@default(now)"]
description: "Created timestamp"
date:
type: datetime
attributes: ["@default(now)"]
description: "Scheduled / effective date (set to the processing instant by the pipeline on done)"
item_id:
type: uuid
attributes: ["@required", "@exclude_from_foreign_key_check"]
description: "The moved item # logical FK to catalog.Item.id"
demand_qty:
type: decimal
attributes: ["@required", "@default(0)", "@precision(18,4)", "@non_negative"]
description: "Initial demand (Odoo product_uom_qty) — the ordered quantity"
quantity:
type: decimal
attributes: ["@default(0)", "@precision(18,4)", "@non_negative"]
description: "Done quantity (Odoo quantity); on done moves the quantity actually moved, which the SLE mint reads"
price_unit:
type: decimal
attributes: ["@default(0)", "@precision(18,6)"]
description: "Unit valuation price the SLE mint uses (moving-average keeps the proven engine as valuation owner)"
forced_value:
type: decimal?
attributes: ["@precision(18,2)"]
description: "Explicit total value the move carries — a document reversal valued at its original amount. When set, the valuation core uses it instead of the average/price-derived carry on the value-bearing legs, so reversing a voucher restores the estate to exactly its pre-movement state"
procure_method:
type: ProcureMethod
attributes: ["@required", "@default(make_to_stock)"]
description: "make_to_stock (serve from stock) / make_to_order (chain a supply move)"
picking_id:
type: uuid?
attributes: ["@foreign_key(Transfer.id)"]
description: "The grouping transfer (picking projection surface, T1)"
origin:
type: string?
attributes: ["@max(200)"]
description: "Source document reference (the minting voucher's number)"
location_id:
type: uuid
attributes: ["@required", "@foreign_key(Location.id)"]
description: "Source location"
location_dest_id:
type: uuid
attributes: ["@required", "@foreign_key(Location.id)"]
description: "Destination location"
partner_id:
type: uuid?
attributes: ["@exclude_from_foreign_key_check"]
description: "Contact (supplier on receipts, customer on deliveries) # logical FK to party.Party.id"
rule_id:
type: uuid?
attributes: ["@foreign_key(RouteRule.id)"]
description: "The procurement rule that minted this move (chained moves cite their rule)"
warehouse_id:
type: uuid?
attributes: ["@foreign_key(Warehouse.id)"]
description: "Warehouse resolving the move's route"
orderpoint_id:
type: uuid?
attributes: ["@foreign_key(ReorderingRule.id)"]
description: "The reordering rule whose scheduler run minted this move (T11)"
move_orig_ids:
type: uuid[]
description: "Upstream moves this one is chained after (MTO chains / backorders) # logical FK to StockMove.id"
move_dest_ids:
type: uuid[]
description: "Downstream moves chained from this one; cancelling a done upstream move must propagate through (guard R19) # logical FK to StockMove.id"
is_inventory:
type: bool
attributes: ["@default(false)"]
description: "True for the adjustment move minted by the quant-driven inventory application (T4) — GL legs land on the adjustment accounts, never COGS"
scrapped:
type: bool
attributes: ["@default(false)"]
description: "True for scrap moves (dest location usage = inventory)"
propagate_cancel:
type: bool
attributes: ["@default(true)"]
description: "Cancelling this move propagates to its chain"
metadata:
type: Metadata
attributes: ["@audit_metadata"]
description: "Audit metadata"
relations:
picking:
type: Transfer
attributes: ["@one", "@foreign_key(picking_id)"]
description: "The grouping transfer"
inverse: moves
move_lines:
type: StockMoveLine[]
attributes: ["@one_to_many"]
description: "The physical execution lines (lot / package / owner detail)"
inverse: move
indexes:
- type: index
fields: [state]
description: "State worklist filtering"
- type: index
fields: [picking_id]
description: "Transfer projection gather (T1)"
- type: index
fields: [item_id]
- type: index
fields: [location_id]
- type: index
fields: [location_dest_id]
- type: index
fields: [procure_method]
description: "Scheduler MTO sweep"
- type: index
fields: [rule_id]
- type: index
fields: [orderpoint_id]
description: "Orderpoint traceability (T11)"
- type: index
fields: [warehouse_id]
- type: index
fields: [posting_state]
description: "Pending/failed GL sweep — the repost worklist (finds moves whose physical leg landed without a settled GL leg)"
- name: StockMoveLine
collection: stock_move_lines
read_only: true
description: "A detailed stock operation line — the physical execution row (lot / package / owner) under a move; qty mirrors the quant reservation (T2 mirror arm, never authoritative)"
fields:
id:
type: uuid
attributes: ["@id", "@default(uuid)"]
description: "Unique move-line identifier"
quantity:
type: decimal
attributes: ["@default(0)", "@precision(18,4)", "@non_negative"]
description: "Reserved/done quantity on this line — the MIRROR arm of the reservation triangle (quant.reserved_quantity stays authoritative, T2)"
quantity_product_uom_id:
type: uuid?
attributes: ["@exclude_from_foreign_key_check"]
description: "Unit of measure for UI display only # logical FK to catalog.UnitOfMeasure.id"
picked:
type: bool
attributes: ["@default(false)"]
description: "Barcode-picked flag"
lot_id:
type: uuid?
attributes: ["@foreign_key(Lot.id)"]
description: "The tracked lot / serial"
package_id:
type: uuid?
attributes: ["@foreign_key(Package.id)"]
description: "Source package"
result_package_id:
type: uuid?
attributes: ["@foreign_key(Package.id)"]
description: "Destination package (packing)"
owner_id:
type: uuid?
attributes: ["@exclude_from_foreign_key_check"]
description: "Consignment owner # logical FK to party.Party.id"
state:
type: MoveState
attributes: ["@default(draft)"]
lifecycle: projection
description: "Mirrored from the parent move's state (projection shape, ADR-0016) — never hand-set; the free-reservation partial index rides this column"
date:
type: datetime
attributes: ["@default(now)"]
description: "Scheduled / effective date"
move_id:
type: uuid
attributes: ["@required", "@foreign_key(StockMove.id)"]
description: "Parent move (the lifecycle owner)"
picking_id:
type: uuid?
attributes: ["@foreign_key(Transfer.id)"]
description: "Denormalized transfer reference (same as move.picking_id) for the transfer's line gather"
location_id:
type: uuid
attributes: ["@required", "@foreign_key(Location.id)"]
description: "Source location"
location_dest_id:
type: uuid
attributes: ["@required", "@foreign_key(Location.id)"]
description: "Destination location"
item_id:
type: uuid
attributes: ["@required", "@exclude_from_foreign_key_check"]
description: "The moved item (denormalized from the move for quant gathering) # logical FK to catalog.Item.id"
metadata:
type: Metadata
attributes: ["@audit_metadata"]
description: "Audit metadata"
relations:
move:
type: StockMove
attributes: ["@one", "@foreign_key(move_id)"]
description: "Parent move"
inverse: move_lines
picking:
type: Transfer
attributes: ["@one", "@foreign_key(picking_id)"]
description: "The grouping transfer"
inverse: move_lines
indexes:
- type: index
name: _free_reservation_index
fields: [state]
where: "state <> 'done'"
description: "Verbatim _free_reservation_index (state) WHERE state <> 'done' — the un-reserved worklist scan"
- type: index
fields: [move_id]
- type: index
fields: [picking_id]
- type: index
fields: [item_id]
- type: index
fields: [lot_id]
description: "Serial/lot trace (T5 serial uniqueness, T9 lot traceability)"
- type: index
fields: [package_id]
- type: index
fields: [location_id]
- type: index
fields: [location_dest_id]
enums:
- name: MoveState
description: "stock.move.state — the 7-state lifecycle (snake_case values; generated transition verbs no-op on PascalCase)"
variants:
- name: draft
description: "New (not yet confirmed)"
default: true
- name: waiting
description: "Waiting on another operation (chained move not ready)"
- name: confirmed
description: "Waiting (confirmed, nothing reserved)"
- name: partially_available
description: "Partially available (some quantity reserved)"
- name: assigned
description: "Available (fully reserved)"
- name: done
description: "Done (quant flips applied, SLE minted through the AccountingPost seam)"
- name: cancel
description: "Cancelled (reservation released)"
- name: ProcureMethod
description: "stock.move.procure_method — how the move is supplied"
variants:
- name: make_to_stock
description: "From stock (reserve existing quants)"
default: true
- name: make_to_order
description: "Trigger a supply rule (chain a new move)"
- name: mts_else_mto
description: "From stock if available, else trigger a supply rule (kept for completeness — added by other modules upstream)"