---
id: ATS-COM-FUL-017
scope: CAP-COM-FUL-017
product_area: commerce
module: fulfillment
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-COM-FUL-017-01
    title: Staff movement validation protects operational types
    level: api
    proves:
      - BR-CAP-COM-FUL-017-01
      - BR-CAP-COM-FUL-017-02
      - BR-CAP-COM-FUL-017-06
      - INV-DOM-COM-FUL-005-03
    preconditions: A stock item exists at a bin.
    steps: >-
      Record allowed receipt, quarantine and scrap, then try shipment type,
      negative receipt and absent-SKU scrap.
    expected: Allowed ledger entries succeed and invalid staff movements fail.
    negative: true
    data_needs: Fixtures for staff movement validation protects operational types.
    bound_to:
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#moves
        quarantined beside on_hand and a scrap off it; refuses what the shelf
        cannot bear (400 naming the rule), a wrong sign (400) and a type staff
        do not record (400)
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        a quantity whose sign the type forbids, by
        inventory_movements_quantity_sign_check, and a type outside the twelve
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#refuses
        a receipt of a negative quantity and a scrap of a positive one (400),
        and a movement of a SKU nothing holds
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#moves
        quarantined and damaged beside on_hand, and a scrap off it; available is
        what is left to sell
      - TST-COM-FUL-057
      - TST-COM-FUL-059
      - TST-COM-FUL-066
    provenance: test
    source: >-
      services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#moves
      quarantined beside on_hand and a scrap off it; refuses what the shelf
      cannot bear (400 naming the rule), a wrong sign (400) and a type staff do
      not record (400)
    category: validation
  - id: ATS-COM-FUL-017-02
    title: Balances and movement history reject direct alteration
    level: api
    proves:
      - BR-CAP-COM-FUL-017-03
      - BR-CAP-COM-FUL-017-04
      - BR-CAP-COM-FUL-017-11
      - INV-DOM-COM-FUL-005-01
      - INV-DOM-COM-FUL-005-02
      - INV-DOM-COM-FUL-005-03
    preconditions: Five units are on hand.
    steps: >-
      Try direct balance edit, six-unit scrap and movement mutations, then
      change item settings.
    expected: Invalid balance and ledger edits fail while settings alone can change.
    negative: true
    data_needs: Fixtures for balances and movement history reject direct alteration.
    bound_to:
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        the application role a balance: UPDATE on on_hand is 42501 (the column
        grant), while its settings are its to change
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        a movement the shelf cannot bear, by inventory_items_balances_check (the
        last line of defence), leaving the balances as they were
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#is
        append-only: the application role cannot update or delete a movement
        (42501)
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#refuses
        a movement the shelf cannot bear, 400 naming the balance rule, writing
        nothing
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#changes
        an item’s settings (and nothing of its balances), 404 for an item the
        tenant has not
      - TST-COM-FUL-059
      - TST-COM-FUL-066
      - TST-COM-FUL-057
    provenance: test
    source: >-
      services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
      the application role a balance: UPDATE on on_hand is 42501 (the column
      grant), while its settings are its to change
    category: invariant
  - id: ATS-COM-FUL-017-03
    title: Movement retries require the same payload
    level: unit
    proves:
      - BR-CAP-COM-FUL-017-05
      - INV-DOM-COM-FUL-005-04
    preconditions: A receipt is stored under an idempotency key.
    steps: >-
      Repeat by matching details and item identity, then change SKU, quantity,
      type or location.
    expected: >-
      Matching retries return the stored movement and changed requests reject
      key reuse.
    negative: true
    data_needs: Fixtures for movement retries require the same payload.
    bound_to:
      - >-
        services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
        the same movement when the retry restates the type, the quantity and the
        item by product, SKU and location
      - >-
        services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
        another movement when the key arrives with another SKU and quantity (the
        pads under the pens’ key)
      - >-
        services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
        another movement when only the quantity differs
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#refuses
        the receipt key reused for another SKU or quantity, 409
        idempotency_key_reused naming the first movement, writing nothing
        (DEF-COM-0452)
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#records
        the receipt once, the item made at A1 with the five on hand, the retry
        answering the same movement unrecorded and no backorder filled
      - TST-COM-FUL-067
      - TST-COM-FUL-066
      - TST-COM-FUL-057
    provenance: test
    source: >-
      services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
      the same movement when the retry restates the type, the quantity and the
      item by product, SKU and location
    category: edge
  - id: ATS-COM-FUL-017-04
    title: Receipt commits the oldest waiting backorders
    level: api
    proves:
      - BR-CAP-COM-FUL-017-07
      - INV-DOM-COM-FUL-005-09
    preconditions: Two units of a SKU are backordered.
    steps: Receive three units into sellable stock.
    expected: >-
      Both backorders become committed oldest first and one unit remains
      available.
    negative: false
    data_needs: Fixtures for receipt commits the oldest waiting backorders.
    bound_to:
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#converts
        both backordered units to committed in the receipt’s transaction, oldest
        order first, leaving one to sell
      - >-
        services-commerce:modules/inventory/tests/e2e/merchandise-placement.e2e.spec.js#fills
        the backordered unit in the receipt’s transaction: committed, counted
        against allocated, four left to sell, both obligations still in progress
      - TST-COM-FUL-066
      - TST-COM-FUL-058
    provenance: test
    source: >-
      services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#converts
      both backordered units to committed in the receipt’s transaction, oldest
      order first, leaving one to sell
    category: interaction
  - id: ATS-COM-FUL-017-05
    title: Transfer preserves linked stock and cost changes
    level: api
    proves:
      - BR-CAP-COM-FUL-017-08
      - INV-DOM-COM-FUL-005-08
    preconditions: Seven source units have two cost layers.
    steps: Move five units to another bin and replay the transfer.
    expected: >-
      One linked movement pair and destination item survive, cost is carried at
      weighted value without posting and repetition moves nothing.
    negative: false
    data_needs: Fixtures for transfer preserves linked stock and cost changes.
    bound_to:
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#writes
        the pair in one transfer: a transfer_out of five on A1’s item and a
        transfer_in of five on A2’s, one id, each naming the other
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#makes
        the destination item, and moves on-hand on both: A1 seven to two, A2
        none to five
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#relieves
        the source’s layers oldest first under transfer_out (four at 1000, one
        at 1301), naming the transfer_out movement
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#writes
        no posting fact for the transfer: nothing leaves the books
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#answers
        a retry under the key with the same transfer and moves nothing twice
      - TST-COM-FUL-064
    provenance: test
    source: >-
      services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#writes
      the pair in one transfer: a transfer_out of five on A1’s item and a
      transfer_in of five on A2’s, one id, each naming the other
    category: invariant
  - id: ATS-COM-FUL-017-06
    title: Transfer guards exclude unavailable or invalid quantities
    level: api
    proves:
      - BR-CAP-COM-FUL-017-09
      - BR-CAP-COM-FUL-017-10
      - BR-CAP-COM-FUL-017-12
    preconditions: Some source stock is allocated and a reader is available.
    steps: >-
      Try over-transfer, allocated stock, same destination, zero, unknown SKU,
      conflicting key and reader transfer.
    expected: >-
      Every invalid request fails without a transfer, including attempts to move
      backorder-filled units.
    negative: true
    data_needs: Fixtures for transfer guards exclude unavailable or invalid quantities.
    bound_to:
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        more than the source has available 409 insufficient_available, and
        writes nothing
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#never
        moves an allocated unit: the two caps left on A1 claimed by an order, a
        transfer of one is refused
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        the same location twice 400 same_location and a quantity of zero 400
        quantity_not_positive
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        a SKU the source location does not hold 400 no_source_item
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        the key reused for another transfer 409 idempotency_key_reused
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        a second transfer of the filled units 409 insufficient_available: they
        are the buyer’s now
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#lets
        a reader list the transfers and refuses it a transfer 403
        permission_missing; a denied principal 403 app_not_permitted; no context
        401; tenant B none
      - TST-COM-FUL-064
      - TST-COM-FUL-057
    provenance: test
    source: >-
      services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
      more than the source has available 409 insufficient_available, and writes
      nothing
    category: permission
  - id: ATS-COM-FUL-017-07
    title: Replenishment creates the second dispatch wave
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-FUL-012-02
      - BR-CAP-COM-FUL-017-07
      - BR-CAP-COM-FUL-018-02
      - BR-CAP-COM-FUL-014-01
    exercises:
      - CAP-COM-FUL-012
      - CAP-COM-FUL-017
      - CAP-COM-FUL-018
      - CAP-COM-FUL-014
    relies_on:
      - ATS-COM-FUL-012-02
      - ATS-COM-FUL-017-04
      - ATS-COM-FUL-018-02
      - ATS-COM-FUL-014-01
    preconditions: A five-shirt order has three available units and two backorders.
    steps: >-
      Ship the first wave, receive two more shirts and create and ship the next
      wave.
    expected: >-
      The second wave contains only the two replenished units; all five
      obligations complete.
    negative: false
    data_needs: >-
      An isolated workspace with the products, records, authority and quantities
      described in the preconditions.
    bound_to:
      - >-
        services-commerce:modules/shipping/tests/e2e/shipping.e2e.spec.js#makes
        the first wave of the three picked units (0..2), charged what the order
        paid, and walks it to the handoff
      - >-
        services-commerce:modules/shipping/tests/e2e/shipping.e2e.spec.js#fills
        the two backorders at the receipt, and the second wave picks the filled
        units at its creation (3..4) and ships them
      - >-
        services-commerce:modules/shipping/tests/e2e/shipping.e2e.spec.js#completes
        the order’s shipping: every unit shipped, every obligation completed,
        the shelf down five, the cost facts emitted per wave
      - TST-COM-FUL-075
    provenance: test
    source:
      - >-
        services-commerce:modules/shipping/tests/e2e/shipping.e2e.spec.js#makes
        the first wave of the three picked units (0..2), charged what the order
        paid, and walks it to the handoff
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#converts
        both backordered units to committed in the receipt’s transaction, oldest
        order first, leaving one to sell
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/merchandise-claims.integration.spec.js#is
        refused insufficient_stock with nothing written when the product
        prohibits backorders (the last cap)
      - >-
        services-commerce:modules/shipping/tests/integration/services/fulfillment/shipping-flow.integration.spec.js#hands
        off: the line handed to the carrier with its whole quantity shipped, the
        units shipped in the ledger, one shipment movement of minus three on the
        item, three fewer on hand
  - id: ATS-COM-FUL-017-08
    title: Returned stock becomes available through the movement ledger
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-FUL-015-05
      - BR-CAP-COM-FUL-017-03
      - BR-CAP-COM-FUL-018-06
    exercises:
      - CAP-COM-FUL-015
      - CAP-COM-FUL-017
      - CAP-COM-FUL-018
    relies_on:
      - ATS-COM-FUL-015-04
      - ATS-COM-FUL-017-02
      - ATS-COM-FUL-018-06
    preconditions: Two shipped shirts return under a restock-permitting authorization.
    steps: >-
      Receive and inspect them, restock to the original sellable location and
      read availability and movements.
    expected: >-
      The returned quantity has a restock entry and is available to buyers;
      receiving alone adds no stock.
    negative: false
    data_needs: >-
      An isolated workspace with the products, records, authority and quantities
      described in the preconditions.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/shipping/tests/integration/services/returns/return-receiving.integration.spec.js#restocks
        the two units at the bin they shipped from: the return_restock movement
        naming the disposition, on hand up two, the two reliefs reversed at 1500
        with the restore_cogs fact of 3000
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        the application role a balance: UPDATE on on_hand is 42501 (the column
        grant), while its settings are its to change
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#answers
        the availability in the storefront’s words: limited with what is left,
        unavailable for more than the shelf has, backorder for the shirt,
        available for a product it does not track
  - id: ATS-COM-FUL-017-09
    title: An inactive warehouse refuses receipt and allocation
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-FUL-018-01
      - INV-DOM-COM-FUL-005-12
    exercises:
      - CAP-COM-FUL-016
      - CAP-COM-FUL-017
      - CAP-COM-FUL-018
    relies_on:
      - ATS-COM-FUL-016-05
      - ATS-COM-FUL-017-01
      - ATS-COM-FUL-018-01
    preconditions: The only sellable location belongs to an inactive warehouse.
    steps: >-
      Attempt a receipt and a placement claim, then repeat after authorized
      activation.
    expected: >-
      Inactive-place work is refused; active-place work is considered under its
      normal stock guards.
    negative: false
    data_needs: >-
      An isolated workspace with the products, records, authority and quantities
      described in the preconditions.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#lets
        a reader list and read everything, and refuses it every write 403
        permission_missing
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#moves
        quarantined beside on_hand and a scrap off it; refuses what the shelf
        cannot bear (400 naming the rule), a wrong sign (400) and a type staff
        do not record (400)
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/merchandise-claims.integration.spec.js#answers
        ok with one committed claim for the SKU and the quantity, under the
        claim ref the line is stamped with
  - id: ATS-COM-FUL-017-10
    title: Place ordering controls the units claimed from received stock
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-FUL-016-05
      - BR-CAP-COM-FUL-017-01
      - BR-CAP-COM-FUL-018-01
    exercises:
      - CAP-COM-FUL-016
      - CAP-COM-FUL-017
      - CAP-COM-FUL-018
    relies_on:
      - ATS-COM-FUL-016-04
      - ATS-COM-FUL-017-01
      - ATS-COM-FUL-018-01
    preconditions: >-
      Two active sellable bins have different pick priorities and receive the
      same SKU.
    steps: >-
      Receive stock in both and claim two units, then inspect their item and
      location references.
    expected: The claim uses the earlier sellable location before the later one.
    negative: false
    data_needs: >-
      An isolated workspace with the products, records, authority and quantities
      described in the preconditions.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/inventory/tests/integration/services/persistence/inventory-configuration.integration.spec.js#answers
        the sellable locations across warehouses in allocation order: the main
        warehouse (priority 1) before the depot (2), bins by pick priority,
        quarantine and returns left out
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#moves
        quarantined beside on_hand and a scrap off it; refuses what the shelf
        cannot bear (400 naming the rule), a wrong sign (400) and a type staff
        do not record (400)
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/merchandise-claims.integration.spec.js#answers
        ok with one committed claim for the SKU and the quantity, under the
        claim ref the line is stamped with
  - id: ATS-COM-FUL-017-11
    title: A shelf count corrects the ledger and changes availability
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-FUL-019-02
      - BR-CAP-COM-FUL-017-03
      - BR-CAP-COM-FUL-018-06
    exercises:
      - CAP-COM-FUL-019
      - CAP-COM-FUL-017
      - CAP-COM-FUL-018
    relies_on:
      - ATS-COM-FUL-019-01
      - ATS-COM-FUL-017-02
      - ATS-COM-FUL-018-06
    preconditions: A sellable item holds ten units and a scoped count observes eight.
    steps: 'Complete the count, inspect its correction movement and read availability.'
    expected: >-
      The count retains one correction and buyers see the corrected stock;
      repeated completion changes nothing.
    negative: false
    data_needs: >-
      An isolated workspace with the products, records, authority and quantities
      described in the preconditions.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#opens
        the count open with no lines, records the lines with what the shelf
        expected, and completes it with one correction for the variance
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        the application role a balance: UPDATE on on_hand is 42501 (the column
        grant), while its settings are its to change
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#answers
        the availability in the storefront’s words: limited with what is left,
        unavailable for more than the shelf has, backorder for the shirt,
        available for a product it does not track
  - id: ATS-COM-FUL-017-12
    title: Imported opening stock participates in the normal ledger
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-FUL-020-12
      - BR-CAP-COM-FUL-017-03
    exercises:
      - CAP-COM-FUL-020
      - CAP-COM-FUL-017
      - CAP-COM-FUL-016
    relies_on:
      - ATS-COM-FUL-020-09
      - ATS-COM-FUL-017-02
      - ATS-COM-FUL-016-05
    preconditions: >-
      An implementation workspace has a migrated warehouse, location and product
      source identity.
    steps: >-
      Import twelve units with two damaged, read back the source key and inspect
      live balances and movements.
    expected: >-
      The item has ten available units, backed by count and damage entries; an
      inactive destination refuses a new item.
    negative: false
    data_needs: >-
      An isolated workspace with the products, records, authority and quantities
      described in the preconditions.
    bound_to:
      - >-
        services-commerce:modules/data-operations/tests/integration/services/operations/inventory-import.integration.spec.js#opens
        the mug’s item through the ledger: a count correction of twelve and a
        damage of two, so the trigger holds ten available
      - >-
        services-commerce:modules/data-operations/tests/integration/services/operations/inventory-import.integration.spec.js#opens
        the cap’s item under the product’s own SKU at the default location, with
        no movement for a count of nought
      - >-
        services-commerce:modules/data-operations/tests/integration/services/operations/inventory-import.integration.spec.js#refuses
        the same key loaded again as that item’s invalid value, moving nothing
      - >-
        services-commerce:modules/data-operations/tests/integration/services/operations/inventory-import.integration.spec.js#refuses
        an item at an inactive location as that item’s refusal, as the implicit
        creation would
      - TST-COM-FUL-007
    provenance: test
    source:
      - >-
        services-commerce:modules/data-operations/tests/integration/services/operations/inventory-import.integration.spec.js#opens
        the mug’s item through the ledger: a count correction of twelve and a
        damage of two, so the trigger holds ten available
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        the application role a balance: UPDATE on on_hand is 42501 (the column
        grant), while its settings are its to change
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#lets
        a reader list and read everything, and refuses it every write 403
        permission_missing
evidence:
  - repository: services-commerce
    tests:
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#moves
        quarantined beside on_hand and a scrap off it; refuses what the shelf
        cannot bear (400 naming the rule), a wrong sign (400) and a type staff
        do not record (400)
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        a quantity whose sign the type forbids, by
        inventory_movements_quantity_sign_check, and a type outside the twelve
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#refuses
        a receipt of a negative quantity and a scrap of a positive one (400),
        and a movement of a SKU nothing holds
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#moves
        quarantined and damaged beside on_hand, and a scrap off it; available is
        what is left to sell
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        the application role a balance: UPDATE on on_hand is 42501 (the column
        grant), while its settings are its to change
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#refuses
        a movement the shelf cannot bear, by inventory_items_balances_check (the
        last line of defence), leaving the balances as they were
      - >-
        services-commerce:modules/inventory/tests/integration/db/inventory-tables.table.integration.spec.js#is
        append-only: the application role cannot update or delete a movement
        (42501)
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#refuses
        a movement the shelf cannot bear, 400 naming the balance rule, writing
        nothing
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#changes
        an item’s settings (and nothing of its balances), 404 for an item the
        tenant has not
      - >-
        services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
        the same movement when the retry restates the type, the quantity and the
        item by product, SKU and location
      - >-
        services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
        another movement when the key arrives with another SKU and quantity (the
        pads under the pens’ key)
      - >-
        services-commerce:modules/inventory/tests/unit/stock/staff-movement-keys.spec.js#is
        another movement when only the quantity differs
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#refuses
        the receipt key reused for another SKU or quantity, 409
        idempotency_key_reused naming the first movement, writing nothing
        (DEF-COM-0452)
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#records
        the receipt once, the item made at A1 with the five on hand, the retry
        answering the same movement unrecorded and no backorder filled
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/staff-movements.integration.spec.js#converts
        both backordered units to committed in the receipt’s transaction, oldest
        order first, leaving one to sell
      - >-
        services-commerce:modules/inventory/tests/e2e/merchandise-placement.e2e.spec.js#fills
        the backordered unit in the receipt’s transaction: committed, counted
        against allocated, four left to sell, both obligations still in progress
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#writes
        the pair in one transfer: a transfer_out of five on A1’s item and a
        transfer_in of five on A2’s, one id, each naming the other
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#makes
        the destination item, and moves on-hand on both: A1 seven to two, A2
        none to five
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#relieves
        the source’s layers oldest first under transfer_out (four at 1000, one
        at 1301), naming the transfer_out movement
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#writes
        no posting fact for the transfer: nothing leaves the books
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#answers
        a retry under the key with the same transfer and moves nothing twice
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        more than the source has available 409 insufficient_available, and
        writes nothing
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#never
        moves an allocated unit: the two caps left on A1 claimed by an order, a
        transfer of one is refused
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        the same location twice 400 same_location and a quantity of zero 400
        quantity_not_positive
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        a SKU the source location does not hold 400 no_source_item
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        the key reused for another transfer 409 idempotency_key_reused
      - >-
        services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#refuses
        a second transfer of the filled units 409 insufficient_available: they
        are the buyer’s now
      - >-
        services-commerce:modules/inventory/tests/e2e/inventory.e2e.spec.js#lets
        a reader list the transfers and refuses it a transfer 403
        permission_missing; a denied principal 403 app_not_permitted; no context
        401; tenant B none
coverage:
  rule: 'gap: FND-COM-FUL-072'
  transition: 'gap: FND-COM-FUL-072'
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-COM-FUL-072'
  boundary: 'gap: FND-COM-FUL-072'
  concurrency: 'gap: FND-COM-FUL-072'
  invariant: covered
  interaction: covered
coverage_notes:
  rule: >-
    No scenario specifically isolates this dimension; the coverage finding
    retains the missing proof.
  transition: >-
    No scenario specifically isolates this dimension; the coverage finding
    retains the missing proof.
  failure: >-
    No scenario specifically isolates this dimension; the coverage finding
    retains the missing proof.
  boundary: >-
    No scenario specifically isolates this dimension; the coverage finding
    retains the missing proof.
  concurrency: >-
    No scenario specifically isolates this dimension; the coverage finding
    retains the missing proof.
transition_audit:
  - model: STATE-COM-FUL-009
    from: held
    to: committed
    source: >-
      services-commerce:modules/inventory/db/inventory-allocations.table.ts
      (commerce_commit_claims)
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: held
    to: released
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-allocations.ts:expireHolds
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: committed
    to: picked
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-allocations.ts:pickUnitsOfLine
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: backordered
    to: committed
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-allocations.ts:fillBackorders
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: picked
    to: shipped
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-allocations.ts:shipUnits
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: committed
    to: released
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-allocations.ts:releaseFencedUnitsOfLine
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: picked
    to: released
    source: >-
      services-commerce:docs/migrations/inventory/README.md (D-P7-24 amendment
      of D-P8I-13)
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
  - model: STATE-COM-FUL-009
    from: backordered
    to: released
    source: >-
      services-commerce:modules/inventory/db/inventory-allocations.table.ts
      (commerce_release_claims)
    coverage: 'gap: FND-COM-FUL-072'
    scenarios: []
permission_audit:
  - key: commerce.inventory.read
    where_checked: api
    coverage: 'gap: FND-COM-FUL-072'
    note: >-
      Per-key positive and negative coverage is not separately mapped;
      permission scenarios retain the observed aggregate tests.
  - key: commerce.inventory.create
    where_checked: api
    coverage: 'gap: FND-COM-FUL-072'
    note: >-
      Per-key positive and negative coverage is not separately mapped;
      permission scenarios retain the observed aggregate tests.
  - key: commerce.inventory.update
    where_checked: api
    coverage: 'gap: FND-COM-FUL-072'
    note: >-
      Per-key positive and negative coverage is not separately mapped;
      permission scenarios retain the observed aggregate tests.
related_audit:
  - capability: CAP-COM-FUL-018
    scenarios:
      - ATS-COM-FUL-017-07
      - ATS-COM-FUL-017-08
      - ATS-COM-FUL-017-09
      - ATS-COM-FUL-017-10
      - ATS-COM-FUL-017-11
  - capability: CAP-COM-FUL-019
    scenarios:
      - ATS-COM-FUL-017-11
invariant_audit:
  - invariant: INV-DOM-COM-FUL-005-01
    scenarios:
      - ATS-COM-FUL-017-02
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/db/inventory-items.table.ts (column
      grants, apply_inventory_movement, apply_inventory_allocation)
  - invariant: INV-DOM-COM-FUL-005-02
    scenarios:
      - ATS-COM-FUL-017-02
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/db/inventory-items.table.ts:inventory_items_balances_check
  - invariant: INV-DOM-COM-FUL-005-03
    scenarios:
      - ATS-COM-FUL-017-01
      - ATS-COM-FUL-017-02
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/db/inventory-movements.table.ts:inventory_movements_quantity_sign_check
  - invariant: INV-DOM-COM-FUL-005-04
    scenarios:
      - ATS-COM-FUL-017-03
    coverage: covered
    provenance: documented
    source: 'services-commerce:docs/migrations/inventory/README.md#D-P8I-22'
  - invariant: INV-DOM-COM-FUL-005-05
    scenarios:
      - ATS-COM-FUL-018-05
    coverage: covered
    provenance: observed
    source: 'services-commerce:modules/inventory/db/inventory-allocations.table.ts'
  - invariant: INV-DOM-COM-FUL-005-06
    scenarios:
      - ATS-COM-FUL-018-03
    coverage: covered
    provenance: documented
    source: 'services-commerce:docs/migrations/inventory/README.md#D-P8I-21'
  - invariant: INV-DOM-COM-FUL-005-07
    scenarios:
      - ATS-COM-FUL-014-01
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-allocations.ts:shipUnits
  - invariant: INV-DOM-COM-FUL-005-08
    scenarios:
      - ATS-COM-FUL-017-05
    coverage: covered
    provenance: observed
    source: 'services-commerce:modules/inventory/services/stock/transfer-stock.ts'
  - invariant: INV-DOM-COM-FUL-005-09
    scenarios:
      - ATS-COM-FUL-017-04
    coverage: covered
    provenance: observed
    source: 'services-commerce:modules/inventory/services/stock/staff-movements.ts'
  - invariant: INV-DOM-COM-FUL-005-10
    scenarios:
      - ATS-COM-FUL-019-01
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/services/persistence/inventory-counts.ts:completeCount
  - invariant: INV-DOM-COM-FUL-005-11
    scenarios:
      - ATS-COM-FUL-019-02
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/db/inventory-exceptions.table.ts:inventory_exceptions_open_uidx
  - invariant: INV-DOM-COM-FUL-005-12
    scenarios:
      - ATS-COM-FUL-016-06
      - ATS-COM-FUL-017-09
      - ATS-COM-FUL-018-12
    coverage: covered
    provenance: observed
    source: >-
      services-commerce:modules/inventory/services/persistence/warehouses.ts;
      inventory-locations.ts
---

# Keep stock movements and transfers accountable: Capability Test Specification

## Coverage

The category map distinguishes available cases from missing proof. A covered category has a scenario; the lifecycle and permission audits retain gaps in exhaustive coverage. Proposed cases describe the intended verification and carry no execution result.

| Rule | Scenarios |
|---|---|
| `BR-CAP-COM-FUL-017-01` | `ATS-COM-FUL-017-01`, `ATS-COM-FUL-017-09`, `ATS-COM-FUL-017-10` |
| `BR-CAP-COM-FUL-017-02` | `ATS-COM-FUL-017-01`, `ATS-COM-FUL-017-09`, `ATS-COM-FUL-017-10` |
| `BR-CAP-COM-FUL-017-03` | `ATS-COM-FUL-017-02`, `ATS-COM-FUL-017-08`, `ATS-COM-FUL-017-11`, `ATS-COM-FUL-017-12` |
| `BR-CAP-COM-FUL-017-04` | `ATS-COM-FUL-017-02`, `ATS-COM-FUL-017-08`, `ATS-COM-FUL-017-11`, `ATS-COM-FUL-017-12` |
| `BR-CAP-COM-FUL-017-05` | `ATS-COM-FUL-017-03` |
| `BR-CAP-COM-FUL-017-06` | `ATS-COM-FUL-017-01`, `ATS-COM-FUL-017-09`, `ATS-COM-FUL-017-10` |
| `BR-CAP-COM-FUL-017-07` | `ATS-COM-FUL-017-04`, `ATS-COM-FUL-017-07` |
| `BR-CAP-COM-FUL-017-08` | `ATS-COM-FUL-017-05` |
| `BR-CAP-COM-FUL-017-09` | `ATS-COM-FUL-017-06` |
| `BR-CAP-COM-FUL-017-10` | `ATS-COM-FUL-017-06` |
| `BR-CAP-COM-FUL-017-11` | `ATS-COM-FUL-017-02`, `ATS-COM-FUL-017-08`, `ATS-COM-FUL-017-11`, `ATS-COM-FUL-017-12` |
| `BR-CAP-COM-FUL-017-12` | `ATS-COM-FUL-017-06` |

## Interactions With Related Capabilities

The interaction cases exercise allocate stock and explain what buyers can obtain; reconcile shelf counts and stock findings. They vary the upstream record or outcome and inspect the resulting work across the capability boundary. Workflow crossings also include stock, financial or application records when the flow depends on them. Existing test bindings retain their source anchors so a reviewer can distinguish supporting assertions from a complete workflow run.
