dodil/erp-brick-inventory

DODIL ERP brick: inventory

AGENTS.md

Instructions for agents working on this brick

Built from the inventory module spec (erp-method modules/inventory, version 3, signed 2026-10-22). Read erp-platform's BRICKS.md first: the rules and why each one exists. A process change goes through erp-compose change propose, the customer's new signature, then the code.

Where a change goes

You are changing...It goes in
a business rule on values, valuation, locations, removaldomain/ (plain Python) and its test
the move engine (claims, valuation, tracking)application/ledger.py
who may do something, which rowsthe use case (permission= or common.need_read), never a role's name
a use case for people and agentsa function in application/<part>.py with @INV.use_case, imported in use_cases.py
how another module is reachedinfrastructure/seams.py only (spec routes, never their tables)
a column or tableinfrastructure/tables.py, then python scripts/gen_schema.py and a new migrations/NNNNN_<name>/
an AI stepinfrastructure/ai_steps.py and the use case that calls ask()

The rules that must not be undone

  • Stock is a ledger. inv_move is never updated or deleted; a mistake is a reversing move that names the original. On hand is derived from moves (Repository.stock); no quantity is stored. inv_valuation, inv_claim, inv_number_series, inv_reservation are append-only too, written with a PLAIN INSERT: a key another writer took is a lost race (ClaimLost).
  • Plan from reads, write at the end (application/unit.py): DataK3 has no read-your-writes in a transaction. Claims are taken first (flush_races) and before any neighbour is told; a lost attempt is compensated and re-run on a fresh read.
  • Every quantity is in the product's base unit, exact (Fraction); a conversion that does not fit four places is refused, never rounded. Money is round_half_up once.
  • The sales order hears of a delivery only from here; a receipt is recorded only by purchasing (scan_delivery asks it to; purchasing posts each line back through POST /receipts). Nothing writes gl_, pur_, so_ or prod_ tables.
  • Cost and value are projected out for callers without inv:valuation:read; a caller without inv:stock:read gets a 404.
  • Approvers are compared by email AND sign-in subject (unit.identities).
  • 'Today' is infrastructure/business_time.today() (the one business_timezone), never date.today().
  • AI steps only suggest; no model is called in tests.