Skip to content

CAP-COM-FUL-017Keep stock movements and transfers accountable

Markdown

CAP-COM-FUL-017

Keep stock movements and transfers accountable

Markdown
  • DocumentDraft
  • Product intentProposed
  • ImplementationObserved

KindCapabilityProduct areaCommerceModuleFulfillment, Shipping and InventoryRevision1

Read atservices-commerce c8b66a98

The Formal Contract

The narrative above explains; the sections below are authoritative — the rules, scenarios and tests this document binds.

Outcomes
Movement history explains physical stock changes.Replenishment commits waiting backorders in purchase order.
Surfaces
api
Intent sources
services-commerce:docs/migrations/inventory/README.md#D-P8I-14 (decision M, Andrew 2026-10-18)

Controls

20 controls apply, from the corpus control catalogue.

TC-AZ-001 TC-AZ-002 TC-AZ-003 TC-AZ-004 TC-AZ-005 TC-AZ-006 TC-TEN-001 TC-TEN-002 TC-TEN-003 TC-CRY-001 TC-DAT-001 TC-DAT-002 TC-DAT-003 TC-LOG-004 TC-LOG-005 TC-PI-001 TC-PI-002 TC-PI-003 TC-PRV-001 TC-PRV-002

Permissions

KeyChecked atScope
commerce.inventory.readapitenant
commerce.inventory.createapitenant
commerce.inventory.updateapitenant

Business Rules

12 rules, 12 to be proved before approval. A rule's product intent is derived from its origin: 0 confirmed by a product decision, the rest inheriting this capability's proposed.

IdRuleKindEnforced atProvenance
01must prove
Proposed
Staff can enter receipts, adjustments, quarantine movements, damage, scrap and write-offs, while shipment, restock, transfer and count movements are reserved for their owning processes.
no proof recorded yet
validationapitest
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)
02must prove
Proposed
Receipt, restock, transfer-in and quarantine-in quantities are positive; shipment, transfer-out, scrap, write-off and quarantine-out quantities are negative; adjustments, counts and damage permit either sign.
no proof recorded yet
validationdatabasetest
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
03must prove
Proposed
Ledger operations alone change balances, the application role cannot write them directly, and database checks reject impossible movements and negative available stock.
no proof recorded yet
invariantdatabasetest
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
04must prove
Proposed
Movement records are immutable after insertion.
no proof recorded yet
invariantdatabasetest
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)
05must prove
Proposed
Repeated movement keys return the original only when type, quantity, item and location agree, otherwise rejecting reuse with the first movement identified.
origin: DEF-COM-0452 (acceptance defect)
no proof recorded yet
invariantservice, databasetest
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)
06must prove
Proposed
Receipt can create an absent location item, while other movement types reject a SKU not held there.
no proof recorded yet
validationservicetest
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
07must prove
Proposed
Stock arriving at a sellable location commits waiting backorders in oldest-order order within the receipt transaction.
no proof recorded yet
calculationservice, databasetest
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
08must prove
Proposed
Transfer atomically records linked out and in movements between locations, creates a destination item if necessary and moves cost layers without an Accounting posting.
origin: Decision M, Andrew 2026-10-18 ("build the transfer now"): services-commerce:docs/migrations/inventory/README.md#D-P8I-14
no proof recorded yet
calculationservice, databasetest
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
09must prove
Proposed
Transfer rejects insufficient available stock, identical locations, quantities below one, missing reasons, unknown or inactive destinations and absent source SKUs; held, allocated, quarantined and damaged units cannot transfer, and conflicting key reuse is rejected.
origin: Decision M, Andrew 2026-10-18 ("build the transfer now"): services-commerce:docs/migrations/inventory/README.md#D-P8I-14
no proof recorded yet
validationapi, serviceobserved
10must prove
Proposed
Transfer into a sellable location fills waiting backorders, whose committed units cannot subsequently transfer.
origin: Decision M, Andrew 2026-10-18 ("build the transfer now"): services-commerce:docs/migrations/inventory/README.md#D-P8I-14
no proof recorded yet
calculationservicetest
services-commerce:modules/inventory/tests/integration/services/stock/inventory-transfers.integration.spec.js#fills the two backorders in the transfer’s transaction, the units allocated on A1 at their cost
11must prove
Proposed
Item settings can be edited independently, but balances remain controlled by movement and allocation ledgers.
no proof recorded yet
invariantapi, databasetest
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
12must prove
Proposed
Movement and transfer creation require administrator inventory-create authority, while readers can inspect movements, transfers and items.
no proof recorded yet
permissionapiobserved

Scenarios

IdGivenWhenThenProvenance
01Two shirts are waiting on backorder.Receive three units into sellable stock.The older backorders are committed first and one unit remains available.test
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

Edge Cases

IdGivenWhenThenProvenance
02A stored receipt key is supplied for a different SKU.Try to record the changed receipt.The conflict identifies the original movement and changes no stock.test
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)

The Markdown files under specs/ in rhythmsoftware/rhythm-v2-docs are the canonical source of truth. This reader is a generated, read-only projection of them.