backbone-inventory 0.17.2

Inventory: stock ledger of record (SLE) + moving-average valuation; emits COGS/asset AccountingPost (Delivery/Receipt -> GL)
Documentation
# =============================================================================
# Domain: Inventory
# Entities: Scrap (Odoo `stock.scrap`), ScrapReasonTag (stock.scrap.reason.tag)
# =============================================================================
# The disposal family. Scrap is the record of a quantity moved OUT of stock to
# a scrap location; unlike the picking, its lifecycle is trivial draft → done
# with no cancel (`_action_done` mints or reuses an is_scrap stock move from
# location_id to scrap_location_id and validates it — the move is the
# load-bearing artifact, stored back on `move_id`). The actual stock mutation
# rides the ONE move engine: no second estate, no direct quant/SLE writes.
# scrap_qty must be > 0 (spec stock-business-logic.md §8).
#
# ScrapReasonTag is a flat unique-named tag set — guard R7 (name unique).
#
# Tenancy: none, by design (ADR-0029). The module is tenant-agnostic; the
# composing service's decorator owns org scoping. Scraps are plain
# org-scoped rows; ScrapReasonTag is a shared master — root-anchored under
# allow_root, visible to every entitled scope through the scope union.
# =============================================================================

models:
  # ===========================================================================
  # Entity: Scrap — a disposal record (draft → done, no cancel)
  # ===========================================================================
  - name: Scrap
    collection: scraps
    read_only: true
    description: "A scrap (disposal) record — moves a quantity to a scrap location via a scrapped stock move minted and validated through the ONE move engine (the move is the load-bearing artifact, stored on move_id)"

    fields:
      id:
        type: uuid
        attributes: ["@id", "@default(uuid)"]
        description: "Unique scrap identifier"

      name:
        type: string
        attributes: ["@required", "@max(40)", "@default(\"/\")"]
        description: "Reference (sequence-filled; unique per org unit)"

      state:
        type: ScrapState
        attributes: ["@default(draft)"]
        lifecycle: hand_set
        description: "draft / done — flipped to done by the scrap door's action_done; no cancel state (a wrong scrap is corrected by scrapping back / a return move, never by editing the row)"

      origin:
        type: string?
        attributes: ["@max(200)"]
        description: "Origin document reference"

      date_expected:
        type: datetime
        attributes: ["@required", "@default(now)"]
        description: "Expected processing date"

      item_id:
        type: uuid
        attributes: ["@required", "@exclude_from_foreign_key_check"]
        description: "The item being scrapped # logical FK to catalog.Item.id"

      scrap_qty:
        type: decimal
        attributes: ["@required", "@default(1)", "@precision(18,4)"]
        description: "Quantity to scrap (> 0 — the §8 positivity guard; the door refuses a non-positive quantity)"

      location_id:
        type: uuid
        attributes: ["@required", "@foreign_key(Location.id)"]
        description: "Source location (usage ≠ view; same org unit — R9-location posture)"

      scrap_location_id:
        type: uuid
        attributes: ["@required", "@foreign_key(Location.id)"]
        description: "Scrap destination (an inventory-loss / scrap usage location)"

      lot_id:
        type: uuid?
        attributes: ["@foreign_key(Lot.id)"]
        description: "Lot / serial of the scrapped stock"

      package_id:
        type: uuid?
        attributes: ["@foreign_key(Package.id)"]
        description: "Source package"

      owner_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Consignment owner # logical FK to party.Party.id"

      picking_id:
        type: uuid?
        attributes: ["@foreign_key(Transfer.id)"]
        description: "Optional linked picking"

      move_id:
        type: uuid?
        attributes: ["@foreign_key(StockMove.id)"]
        description: "The scrapped stock move minted and validated by the door (the actual mutation; set when state flips to done)"

      scrap_reason_tag_ids:
        type: uuid[]
        description: "Reason tags # logical m2m to ScrapReasonTag.id (R7 keeps the labels unique)"


      metadata:
        type: Metadata
        attributes: ["@audit_metadata"]
        description: "Audit metadata"

    indexes:
      - type: index
        fields: [state]
      - type: index
        fields: [item_id]
      - type: index
        fields: [picking_id]

  # ===========================================================================
  # Entity: ScrapReasonTag — a flat unique-named reason tag (guard R7)
  # ===========================================================================
  - name: ScrapReasonTag
    collection: scrap_reason_tags
    description: "A reason tag for scrap records (unique label — guard R7, _name_uniq)"

    fields:
      id:
        type: uuid
        attributes: ["@id", "@default(uuid)"]
        description: "Unique tag identifier"

      name:
        type: string
        attributes: ["@required", "@max(150)"]
        description: "Tag label (unique — guard R7, _name_uniq)"

      active:
        type: bool
        attributes: ["@default(true)"]
        description: "Archive support"


      metadata:
        type: Metadata
        attributes: ["@audit_metadata"]
        description: "Audit metadata"

    # No module-namespace indexes: the tag-label unique is org-scoped and lives in
    # the composing service's decorator (ADR-0029) — a module-side unique on the
    # bare name would be stricter than the (org unit, name) slot it owns.
    indexes: []

# =============================================================================
# Enums
# =============================================================================

enums:
  - name: ScrapState
    description: "stock.scrap.state — a trivial hand-set document lifecycle (draft → done, no cancel; ADR-0016 hand_set)"
    variants:
      - name: draft
        description: "Recorded, not yet processed"
        default: true
      - name: done
        description: "The scrapped move is validated (terminal — corrections are reversal moves)"