Skip to content

stock: a yard's stock counts its zones and bins - #149

Merged
futurebuildai merged 24 commits into
refactor/v1from
refactor/g1-yard-stock
Oct 15, 2026
Merged

futurebuildai merged 24 commits into
refactor/v1from
refactor/g1-yard-stock

Conversation

@futurebuildai

@futurebuildai futurebuildai commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

A yard's stock is its own inventory row plus every zone and bin whose branch_id is that yard. The dashboard's Inventory Alerts read one row per location, so a yard holding 112 on its own row and 300 in a zone under it (412 against a reorder point of 150) was reported "Current: 112, Low Stock".

The fix

GetInventoryAlerts groups by product and yard, sums the quantity, compares the sum with the reorder point and reports it. location_id is now the yard (the branch). The serve wire test that read it expects the yard; the contract description and the CONTRACT-CHANGES row say so.

Red first

TestGetInventoryAlerts_SumsTheYardsOwnRowAndItsZones (commit 1, alone): a yard with 112 on its own row and 300 in a zone under it against a reorder point of 150 gives no alert; cut the zone to 20 and the alert reports 132 as LOW_STOCK at the yard. At commit 1 it fails: yard holds 412 against a reorder point of 150, want no alert, got {... CurrentQty:112 ... LOW_STOCK}.

Readers and verdicts

Reader Verdict
Dashboard inventory alerts (dashboard.GetInventoryAlerts) Fixed here, with its leg
Counter search, tiles, catalogue (pos counterStockScope) Already right: sums rows by locations.branch_id
Product list and product page stock (product stockJoin) Already right: sums on hand and allocated over the caller's branches
Reorder list (product.ListBelowReorder) Already right: sums every row in the caller's branches against the reorder point
Reorder recommendations and purchasing suggestions (purchase_order recommendations, via inventory.ListByProduct) Already right: sums on hand less allocated over the rows in the caller's reach
Portal availability (portal catalog, via inventory.ListByProduct) Already right: the same sum
Stock lookup, levels list (inventory.ListLevelsPage, ListInventoryByProduct) and the bin picker Per location by design (a row per bin); not a yard total
Stock across yards (the product's Stock & Locations tab) Already right: sums the levels rows of the caller's yard
Market check's first pick No stock reader of its own in the server; the picker lists products through the product list's summed stock
Reporting builder's inventory source Per location rows by design (the report groups or sums as the author defines it)
Write paths (GetInventory, allocate, fulfil, LockBranchInventory) Per row by design; an absolute count with no location is now refused where the product sits in more than one location (round 1)
Cycle count page (pages/yard/CycleCount.ts) Read the yard's sum and wrote it to one row: wrong with zone stock. Fixed in round 1: it counts by location and names each location
Seeds and product/unitset.go (FROM inventory) Not stock readers for a person: seed deletes and a unit change lock

Gates

Go build, vet, gofmt; dashboard, inventory, product, pos, purchase_order, portal, reporting, apicontract, serve (race) and characterization packages against a fresh database; merge -check, cmd/pending, api-client generate, drift and typecheck; check-shape.

Review round 1

  • P2-1, the count by location. The Cycle count lists each location with its expected figure and writes each count to its own location (a found pallet may be added); the adjust route refuses an absolute count with no location where the product is in several, 422 location_required. Red legs: go test ./internal/inventory -run TestCycleCount (count with no location = 200, want 422) and vitest src/pages/yard/CycleCount.test.ts.
  • P3s. The alert leg's second yard is stocked and an unwalled call proves two rows each at its own yard; a legacy row stays its own alert; each alert carries yard_name and the widget shows it; the contract text says what a branch caller sees for a product its yard holds no row of. Whether reorder is per yard or per company is left to a decision.

Review round 2

  • P2-1: an absolute count that names no location is refused 422 location_required whenever the caller has no branch context (administrator with no header, unbound key, granted user with no header), and where the product sits in several locations in reach; the guard reads through the caller's branch or grants. Legs: go test ./internal/inventory -run TestCount_NoLocationIsRefused (three callers 200, want 422 at their first commit); a caller bound to the yard with one row still counts.
  • P3s: a delta the oldest row cannot take is 422 insufficient_stock (was a bare 500); a count under the allocated figure is 422 below_allocated, and the page warns and holds the submit; a scan adds to the location the person chose (asks which when there are several); "Found elsewhere" offers only the yard's own locations; the failed-adjustment toast carries the server's words; the alert grouping drops a term that changed nothing.

🤖 Generated with Claude Code

futurebuildai and others added 24 commits October 14, 2026 13:17
Item 130, N20. A yard holding 112 on its own row and 300 in a zone is above a
reorder point of 150; the alert reads one row per location and reports it low.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…zones

An inventory row sits at one location, the yard or a zone or bin under it, and
every location carries its yard in branch_id. The alert compared one row with
the reorder point; it now sums every row whose location is in the yard, as the
counter's search does, reports the sum and names the yard. The serve wire test
that read the alert's location expects the yard.

Item 130, N20.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…(red)

Item 130, N20, review P2-1. An absolute count with no location sets the yard's
oldest row: 410 against 112 and 300 made the yard total 710.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
… its yard

The first two legs pin the grouping (they pass on the base); the third is red:
no alert carries its yard's name.

Item 130, N20, review P3s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…s own (red)

Item 130, N20, review P2-1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Item 130, N20, review P3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
An absolute count with no location, for a product held in more than one
location in reach, answers 422 location_required and writes nothing; it used
to set the yard's oldest row, turning a count of 410 against 112 and 300 into
a yard total of 710. A delta with no location keeps the stated picks. The one
older test that pinned the guess now pins the refusal and the delta.

Item 130, N20, review P2-1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
The yard's name (its code when it has none) rides each alert, grouping keeps a
row with no yard apart, and the contract text says what a branch caller sees
for a product its yard holds no row of. The dashboard golden gains yard_name.

Item 130, N20, review P3s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
The page lists the yard's own row and each zone or bin that holds the product
with its own expected figure, writes each count to its own location and can add
a location the product was not in. The alerts widget shows the yard's name.
Generated types and the CONTRACT-CHANGES row follow.

Item 130, N20, review P2-1 and P3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…nd in words below allocated (red)

Item 130, N20, review round 2 P2-1 and P3s. Three callers with no branch
context turn 50 counted as 45 into 95; a delta the oldest row cannot take is a
bare 500; a count under the allocated figure is accepted.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…yard, under allocated is said (red)

Item 130, N20, review round 2 P3-2, P3-3, P3-5. The mock lists the newest row
first, as the server does.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…als speak in words

An absolute count that names no location is refused whenever the caller has no
branch context and where the product sits in several locations in reach, read
through the caller's branch or grants. A delta the oldest row cannot take is a
422 insufficient_stock, a count under the allocated figure a 422
below_allocated. The alert grouping drops a term that changed nothing.

Item 130, N20, review round 2 P2-1 and P3s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…yard, under allocated is said

Item 130, N20, review round 2 P3-2, P3-3, P3-5.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Item 130, N20, review round 2.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Item 130, N20, round 3. The same defect as an absolute count: a location less
row. Also the leg for a user granted yard A and a product held at yard B.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
An adjustment that names no location, from a caller with no branch context,
would create a row with no location. Stock moves keep their own write (they
name their endpoints). The older test that pinned an unbound delta taking the
legacy row now pins the refusal.

Item 130, N20, round 3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
The harness seeded its sales stock through an adjustment with no location. It
now names the yard; the inventory list and the sale's recorded body move with it.

Item 130, N20, round 3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Item 130, N20, round 3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…n location-less (red)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…only when all were written (red)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
A refused count stays on the page with its words; written counts are not sent twice.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
@futurebuildai
futurebuildai merged commit d8bc3f3 into refactor/v1 Oct 15, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant