Users need to enter parts/stock and get results without a disconnected inventory system to manage; StockItem.QuantityOnHand and StockTransaction today aren't read or written by the job workflow anywhere except an auto-discovery fallback that silently assumes unlimited purchasing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.5 KiB
Remove inventory quantity tracking from CutList
Background
StockItem.QuantityOnHand and its StockTransaction ledger (Received/Used/Adjustment/Scrapped/Returned) exist today, but nothing in the job workflow reads or writes them automatically:
- Adding a stock item to a job (
JobStock) never checks or reservesQuantityOnHand— a user can putQuantity = 50on aJobStockrow pointing at aStockItemwith 3 on hand and nothing complains. - Locking a job (
JobService.LockAsync) only stampsLockedAt— no transaction is created,QuantityOnHanddoesn't move. - The only place
QuantityOnHandhas any real effect isCutListPackingService.PackAsync's auto-discovery fallback: when a job has noJobStockrows configured for a material, the packer pulls all activeStockItems for that material, treats the firstQuantityOnHandbars as free ("in stock"), and always adds an additional unlimited bin on top for "to be purchased" — i.e. it silently assumes more can always be bought. - The only way
QuantityOnHandactually changes is a fully separate, fully manual flow (StockItemsControllerreceive/use/adjust/scrap, driven from the/stock/{id}page) that nothing in the Jobs UI ever triggers.
Net effect: inventory quantity is a second, disconnected bookkeeping system that the job workflow neither respects nor maintains, and the auto-discovery fallback actively assumes unlimited purchasing beyond whatever quantity happens to be tracked. Decision: remove quantity tracking entirely. A job's stock should be exactly what the user explicitly enters for that job — nothing assumed, nothing silently topped up — and the user should never need to think about a separate "inventory" system to enter parts, stock, and get results.
Scope
Data model
StockItemdropsQuantityOnHand. KeepsMaterialId,LengthInches,Name,Notes,IsActive,CreatedAt/UpdatedAt— it becomes a pure catalog of known lengths per material (still used to populate the "pick a standard length" dropdown when adding stock to a job).StockTransactionentity andStockTransactionTypeenum: deleted entirely, along with theApplicationDbContext.StockTransactionsDbSetand its EF configuration.JobStockis unchanged in shape. ItsQuantitybecomes the sole source of truth for how many bars are available to a job everywhere it's used: a finite number is a hard ceiling (parts beyond it land inItemsNotPlaced, nothing is silently treated as purchasable), and-1(unlimited) only applies when the user explicitly picks it. This "unlimited" option is extended to catalog-sourcedJobStockrows, which today only allow it for custom-length rows.
Services / API
StockItemServicedropsAddStockAsync,UseStockAsync,AdjustStockAsync,ScrapStockAsync,GetTransactionHistoryAsync,RecalculateQuantityAsync. KeepsGetAllAsync,GetByMaterialAsync,GetByIdAsync,CreateAsync,UpdateAsync,DeleteAsync,ExistsAsync.StockItemsControllerdropsPOST /{id}/receive,POST /{id}/use,POST /{id}/adjust,POST /{id}/scrap,POST /{id}/recalculate,GET /{id}/transactions. Keeps list/get/create/update/delete/by-material.StockItemDtodropsQuantityOnHand;CreateStockItemDtodropsQuantityOnHand.StockTransactionDto,AddStockDto,UseStockDto,AdjustStockDto,ScrapStockDtoare deleted.CutListPackingService.PackAsyncdrops the auto-discovery fallback branch entirely (the "no job-specific stock configured" path that readsQuantityOnHandand adds an unlimited purchasable bin). A material with noJobStockrows configured packs zero stock bins, so all of its parts land inItemsNotPlaced— same code path as today's "no stock available" case. TheIsInStockbin classification (!IsCustomLength && StockItemId.HasValue) is untouched, since it already doesn't depend on quantity.CatalogService:CatalogStockItemDtodropsQuantityOnHand;ImportStockItemsAsync/MapStockItemsstop reading/writing it. Existing seed JSON (alro-catalog.json,oneals-catalog.json) keeps the field in the file for now — it's simply ignored on import.CutList.Mcp/InventoryTools.cs: its ownStockItemDtodropsQuantityOnHand.add_stock_itemdrops thequantityOnHandparameter.add_stockconvenience tool dropsquantityOnHandand theQuantityOnHandfield onAddStockResult— it becomes purely "ensure this material and stock length exist."ApiClientmethods that passquantityOnHandare updated to match. Republished to~/.claude/mcp/CutList.Mcp/per the standard MCP publishing workflow.
UI
/stock(Index): drop the "On Hand" column/badge. Intro copy changes from "tracks how many pieces you have on hand" to describing stock items as the lengths of material available to cut from./stock/{id}(Edit): drop the entire right-hand "Inventory" card (quantity badge, Add/Adjust Stock transaction form, transaction history table). Left-hand details form (Material, Length, Name, Notes) is unchanged.- Job Edit → Stock tab:
SaveStockFromInventoryAsync's quantity validation changes fromQuantity < 1toQuantity < -1 || Quantity == 0(matching custom-stock validation), and the form gains an "Unlimited" option for catalog-sourced rows. - Job Edit → Results tab: the existing "Items Not Placed" warning stays as-is structurally; its copy changes from "No stock lengths available or parts too long" to also cover insufficient configured quantity, since a finite
JobStock.Quantityis now the only thing that can produce unplaced items for a material that does have stock configured.
Migration
One new EF Core migration:
- Drops the
QuantityOnHandcolumn fromStockItems. - Drops the
StockTransactionstable.
This is destructive to any QuantityOnHand/StockTransaction data currently on forge (confirmed acceptable — nothing reads it automatically today, and it isn't otherwise relied upon). Applied immediately after dotnet ef migrations add, per the standard EF workflow, no separate confirmation gate beyond this design doc.
Out of scope
- Any change to the packing algorithm itself (
CutList.Core) — untouched, since bin selection already only consumedStockBinSource.Quantity/IsInStock, notQuantityOnHanddirectly. - Reworking the "Purchase List" /
ToBePurchasedBinsreporting concept — it stays as a classification of catalog-sourced vs. custom-length bins for the print report, which doesn't depend on quantity tracking. - Cleaning up
QuantityOnHandvalues already present inalro-catalog.json/oneals-catalog.jsonseed files — harmless once the import path ignores the field.