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_reservationstable with full resource: model, repo, service, GraphQL queries (stockReservationsByItem,stockReservationsBySource,availableQuantity) and mutations (reserveStock,releaseStockReservation). - New
StockReservationStatusenum (ACTIVE | CONSUMED | RELEASED | EXPIRED). - New
Item.allowNegativeStockboolean (defaultsfalse). Whenfalse,InventoryRepository.update_quantityraisesInsufficientStockErrorfor outflows that would driveon_hand − activeReservationsbelow zero. - New
InvoiceDetail.locationId/InvoiceDetail.lotIdcolumns and corresponding GraphQL fields onInvoiceDetailInput/InvoiceDetail. Threaded throughInvoiceFactory.to_event→InvoiceDetailEventDTO→InvoiceEventHandlerso per-line deductions land at the right location/lot. - New
Path.STOCK_RESERVATIONS/Resource.STOCK_RESERVATIONSRBAC 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 asInsufficientStockErrorso 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_stockwere declared asUUIDcolumns in 20250702_rev1.py but the SQLAlchemy model annotates them asInteger. The revision is patched in place so fresh tenants getINTEGER; existing tenants runscripts/fix_inventory_reorder_column_types.pyto 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— createsstock_reservations, addsitems.allow_negative_stock, addsinvoice_details.location_id/lot_id, addsix_inventory_logs_source_id.
One-off scripts (run after migrations):
scripts/fix_inventory_reorder_column_types.py— converts theinventoryreorder columns fromUUIDtoINTEGERon 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— seedsPathPermissionrows for the newSTOCK_RESERVATIONSpath 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 reservationswould 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). SurfaceInsufficientStockErroras a per-line validation error referencing the item and available qty. - Available-quantity hint —
availableQuantity(itemId, locationId)is now callable. Quote/invoice/order line editors can show "X available" inline. - Reservations primitive —
reserveStock/releaseStockReservationare 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_stockdefault (false) means the negative-stock guard activates the moment the column lands.