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: PackageType (Odoo `stock.package.type`), StorageCategory
#           (stock.storage.category), StorageCategoryCapacity
#           (stock.storage.category.capacity), PutawayRule (stock.putaway.rule)
# =============================================================================
# The packages/storage/putaway master-data family (the register's deferred
# SC-6 constraints live here):
#   PackageType            — box/tote dimensions + barcode (R10 unique,
#                            R16 non-negative dimensions/weight)
#   StorageCategory        — a capacity constraint frame hung on locations
#                            (R17 max_weight ≥ 0, allow_new_product band)
#   StorageCategoryCapacity — one capacity row per item (R8) or per package
#                            type (R9), quantity > 0 (R15); the two targets
#                            are mutually exclusive
#   PutawayRule            — where to store an arriving item / category /
#                            package type; storage_category_id is a STORED
#                            derived copy of the destination location's
#                            category (T12), re-derived on every
#                            location_out write (the R27 write-time-derivation
#                            posture — never a free column)
#
# Tenancy: none, by design (ADR-0029) — the module is tenant-agnostic; all four
# are shared masters. The composing decorator anchors them at the tenant ROOT
# org node (allow_root); every entitled scope sees them via the scope union.
# =============================================================================

models:
  # ===========================================================================
  # Entity: PackageType — the physical container archetype (guard R10, R16)
  # ===========================================================================
  - name: PackageType
    collection: package_types
    description: "A package type (box / tote archetype): dimensions, weight and barcode — the capacity join key for storage categories (guards R10 barcode unique, R16 non-negative dimensions/weight)"

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

      name:
        type: string
        attributes: ["@required", "@max(150)"]
        description: "Type label (e.g. 'Tote M')"

      barcode:
        type: string?
        attributes: ["@max(64)"]
        description: "Scan barcode (unique — guard R10, _barcode_uniq)"

      package_use:
        type: PackageUse
        attributes: ["@default(disposable)"]
        description: "disposable / reusable"

      sequence:
        type: integer
        attributes: ["@default(0)"]
        description: "Ordering"

      length:
        type: decimal?
        attributes: ["@precision(12,4)"]
        description: "Outer length (≥ 0 — guard R16)"

      width:
        type: decimal?
        attributes: ["@precision(12,4)"]
        description: "Outer width (≥ 0 — guard R16)"

      height:
        type: decimal?
        attributes: ["@precision(12,4)"]
        description: "Outer height (≥ 0 — guard R16)"

      max_weight:
        type: decimal?
        attributes: ["@precision(18,4)"]
        description: "Maximum loadable weight (≥ 0 — guard R16)"

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


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

    indexes:
      - type: unique
        fields: [barcode]
        where: "deleted_at IS NULL AND barcode IS NOT NULL"
        description: "R10 — one package type per barcode (_barcode_uniq)"

  # ===========================================================================
  # Entity: StorageCategory — a capacity constraint frame (guard R17)
  # ===========================================================================
  - name: StorageCategory
    collection: storage_categories
    description: "A storage category — the capacity frame locations hang on their storage_category_id (max_weight ≥ 0 — guard R17; allow_new_product band)"

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

      name:
        type: string
        attributes: ["@required", "@max(150)"]
        description: "Category label (e.g. 'Pallet rack L')"

      max_weight:
        type: decimal?
        attributes: ["@precision(18,4)"]
        description: "Maximum weight the frame holds (≥ 0 — guard R17)"

      allow_new_product:
        type: StorageAllowNewProduct
        attributes: ["@default(mixed)"]
        description: "Mixed-goods policy: mixed / empty / same"

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


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

    indexes:

  # ===========================================================================
  # Entity: StorageCategoryCapacity — one capacity row (guards R8, R9, R15)
  # ===========================================================================
  - name: StorageCategoryCapacity
    collection: storage_category_capacities
    description: "A storage-category capacity row — item OR package type × category (mutually exclusive targets), quantity > 0 (guards R8 unique per item, R9 unique per package type, R15 positive quantity)"

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

      storage_category_id:
        type: uuid
        attributes: ["@required", "@foreign_key(StorageCategory.id)"]
        description: "Parent category"

      item_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Item this capacity row is for — mutually exclusive with package_type_id # logical FK to catalog.Item.id"

      package_type_id:
        type: uuid?
        attributes: ["@foreign_key(PackageType.id)"]
        description: "Package type this capacity row is for — mutually exclusive with item_id"

      quantity:
        type: decimal
        attributes: ["@required", "@default(1)", "@precision(18,4)"]
        description: "Capacity quantity (> 0 — guard R15, _positive_quantity)"


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

    indexes:
      - type: unique
        fields: [item_id, storage_category_id]
        where: "deleted_at IS NULL AND item_id IS NOT NULL"
        description: "R8 — one capacity rule per item per category (_unique_product)"
      - type: unique
        fields: [package_type_id, storage_category_id]
        where: "deleted_at IS NULL AND package_type_id IS NOT NULL"
        description: "R9 — one capacity rule per package type per category (_unique_package_type)"
      - type: index
        fields: [storage_category_id]

  # ===========================================================================
  # Entity: PutawayRule — where arriving stock is stored (trigger T12)
  # ===========================================================================
  - name: PutawayRule
    collection: putaway_rules
    description: "A putaway rule — where to store an item / category / package type arriving at a source location; storage_category_id is a stored derived copy of the destination location's category (trigger T12)"

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

      sequence:
        type: integer
        attributes: ["@default(0)"]
        description: "Ordering (the match walk is sequence-leading)"

      location_in_id:
        type: uuid
        attributes: ["@required", "@foreign_key(Location.id)"]
        description: "The location stock arrives at"

      location_out_id:
        type: uuid
        attributes: ["@required", "@foreign_key(Location.id)"]
        description: "The destination (putaway) location — whose storage category T12 derives from"

      item_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Optional — the rule applies to one item # logical FK to catalog.Item.id"

      category_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Optional — the rule applies to an item category # logical FK to catalog.ItemCategory.id"

      package_type_id:
        type: uuid?
        attributes: ["@foreign_key(PackageType.id)"]
        description: "Optional — the rule applies to a package type"

      storage_category_id:
        type: uuid?
        attributes: ["@foreign_key(StorageCategory.id)"]
        description: "STORED derived copy of location_out_id.storage_category_id (trigger T12) — re-derived on every destination write, never a free column"

      sublocation:
        type: PutawaySublocation
        attributes: ["@default(no)"]
        description: "Strategy for choosing a sublocation of the destination: no / last_used / closest_location"

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


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

    indexes:
      - type: index
        fields: [location_in_id]
      - type: index
        fields: [location_out_id]
      - type: index
        fields: [storage_category_id]

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

enums:
  - name: PackageUse
    description: "stock.package.type.package_use"
    variants:
      - name: disposable
        description: "Disposable box"
        default: true
      - name: reusable
        description: "Reusable box (totes)"

  - name: StorageAllowNewProduct
    description: "stock.storage.category.allow_new_product"
    variants:
      - name: mixed
        description: "Allow mixed items"
        default: true
      - name: empty
        description: "Only when the location is empty"
      - name: same
        description: "Only when all stored items are the same"

  - name: PutawaySublocation
    description: "stock.putaway.rule.sublocation — how a sublocation of the destination is chosen"
    variants:
      - name: no
        description: "Use the destination as-is"
        default: true
      - name: last_used
        description: "The sublocation this item was last stored in"
      - name: closest_location
        description: "The nearest free sublocation"