Skip to content

1.1.0

Released: staging (target promotion: TBD)

Compared to 1.0.1.

Highlights

  • Inventory safety, Tier 1: invoices can no longer drive stock below zero unless the item explicitly opts in, and there's a first-class reservation primitive (StockReservation) for quotes/orders to build on.
  • Per-invoice-line location and lot: invoice lines now carry locationId / lotId, so multi-location and LOT/SERIAL items deduct from the right place instead of always pulling from the default location.

Added

Inventory safety, Tier 1 (feature page)

  • New stock_reservations table with full resource: model, repo, service, GraphQL queries (stockReservationsByItem, stockReservationsBySource, availableQuantity) and mutations (reserveStock, releaseStockReservation).
  • New StockReservationStatus enum (ACTIVE | CONSUMED | RELEASED | EXPIRED).
  • New Item.allowNegativeStock boolean (defaults false). When false, InventoryRepository.update_quantity raises InsufficientStockError for outflows that would drive on_hand − activeReservations below zero.
  • New InvoiceDetail.locationId / InvoiceDetail.lotId columns and corresponding GraphQL fields on InvoiceDetailInput / InvoiceDetail. Threaded through InvoiceFactory.to_eventInvoiceDetailEventDTOInvoiceEventHandler so per-line deductions land at the right location/lot.
  • New Path.STOCK_RESERVATIONS / Resource.STOCK_RESERVATIONS RBAC enums.
  • New helper InventoryRepository.get_available_quantity(itemId, locationId) (on_hand minus active reservations).

Schema (selected)

  • New types: StockReservation, StockReservationInput, StockReservationStatus.
  • New queries: stockReservationsByItem, stockReservationsBySource, availableQuantity.
  • New mutations: reserveStock, releaseStockReservation.
  • New fields: Item.allowNegativeStock, InvoiceDetail.locationId, InvoiceDetail.lotId, InvoiceDetailInput.locationId, InvoiceDetailInput.lotId, ItemInput.allowNegativeStock.

Changed

  • Invoice creation and update now refuse to drive stock below zero for items where allowNegativeStock = false. Errors surface as InsufficientStockError so the frontend can show per-line validation.
  • inventory_logs(source_id) is now indexed (ix_inventory_logs_source_id) — reversal lookups (delete_inventory_logs) drop from full-scan to indexed seek.

Fixed

  • inventory.reorder_level / reorder_quantity / lead_time / min_stock / max_stock were declared as UUID columns in 20250702_rev1.py but the SQLAlchemy model annotates them as Integer. The revision is patched in place so fresh tenants get INTEGER; existing tenants run scripts/fix_inventory_reorder_column_types.py to convert. These fields are unused, so the cast is safe (NULL → NULL).

Migrations

Run on top of 1.0.1:

  • add_inventory_safety_tier_1 — creates stock_reservations, adds items.allow_negative_stock, adds invoice_details.location_id / lot_id, adds ix_inventory_logs_source_id.

One-off scripts (run after migrations):

  • scripts/fix_inventory_reorder_column_types.py — converts the inventory reorder columns from UUID to INTEGER on tenants that ran the original buggy 20250702_rev1. The script is idempotent and skips tenants that are already correct.
  • scripts/backfill_stock_reservations_path_permissions.py — seeds PathPermission rows for the new STOCK_RESERVATIONS path on every tenant/role.

Apply migrations with task migrate-dev against staging, or task migrate-prod once promoted.

Frontend impact

Required wiring before this version reaches users:

  • Item form — new "Allow selling below zero stock" checkbox (defaults off). Tooltip should explain that disabling means invoices will be rejected once available = on_hand − active reservations would go negative.
  • Invoice line editor — optional Location picker per line (defaults to default location) and a Lot picker for LOT/SERIAL items (fed from getInventoryLotsByItemId). Surface InsufficientStockError as a per-line validation error referencing the item and available qty.
  • Available-quantity hintavailableQuantity(itemId, locationId) is now callable. Quote/invoice/order line editors can show "X available" inline.
  • Reservations primitivereserveStock / releaseStockReservation are exposed but not auto-consumed in this tier; downstream quote/order screens can opt in (e.g. reserve on quote acceptance, release on quote rejection) when they're ready.
  • No breaking changes — all new fields default safely; existing invoices and items continue to behave as before unless the new flags are flipped.

Versioning notes

  • MINOR bump: new feature, additive GraphQL schema, no breaking changes.
  • Bumped via uv run python scripts/bump_version.py 1.1.0 --changelog-stub.
  • Deploy order: migrations first, then backfill script, then app. The allow_negative_stock default (false) means the negative-stock guard activates the moment the column lands.