Repository navigation
The warehouse floor on a handheld: scan to receive, count, put away, pick and load; part and failed deliveries and return pickups on a run; SonarQube removed - #34
Merged
Conversation
…abels, the stock ledger check and the PWA manifest (migration follows with the last step) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…assembleUnits, box notes), one receive function for the desk and the handheld, and the handheld receive Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th Adjust inventory, counted-absent and committed shortfalls resynced) and the Oversold count flag Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p their order), stray-box notes, and move modes inherited down the place tree Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ray boxes reunited), ticking the run's pick list, and loading a run once nothing is left to pull Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s as run stops A delivery stop now says what went, line by line. Fewer than all is a part delivery: the units that went leave stock from the loading bay first and OrderLineItem.fulfilledQty goes up; the rest stays committed to the order, and a DELIVERY appointment for it goes back in the dispatch pool. The last units are the handover, which now consumes only what was still to fulfil. getActiveOrderLines returns the open quantity (ordered less fulfilled), so a resync no longer commits stock again for units already delivered. Completing a stop claims it first (status set where it was not COMPLETED), so a retry or two racing taps cannot hand the same goods over twice. A stop that could not be done comes off the run with the reason on the order (DELIVERY_FAILED); its appointment goes back in the pool and the stock stays the order's. A return's pickup can go on a run (RETURN_PICKUP appointment, linked by ServiceAppointment.returnId): completing it marks the return picked up and is never a handover, and its order's lines are kept off the pull list and picks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One additive migration for all of PR 4's schema (StockAction and StockMovement.actionKey, Product.boxesPerUnit, BoxNote, StockCount and its lines, RETURN_PICKUP and ServiceAppointment.returnId). Checked by migrate deploy into an empty scratch database, then migrate diff against the schema: no difference; the demo seed and seed coverage pass on it. Runbooks: inventory (phases 15-20 and "The handheld floor"), service dispatch (run stop outcomes and return pickups), purchasing (the one receive function), printing (label contexts, ZPL escaping, routing), reporting (the Oversold count flag), returns (two endpoints said to be ungated are gated on sales.return), revenue recognition (the stop route and fulfilledQty have changed under its plan). DECISIONS 21 (count by place; the store-grain count stays for the cutover), 22 (boxes are evidence), 23 (a part delivery is not the handover). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ound - Label routing fell back to the first printer anywhere, so a warehouse place's label printed at a showroom. It now refuses, naming what to set up, when the store has no printer loaded with the template's size (rule 63). - The driver's "Mark Arrived" was refused on every stop: the transition table allowed only PENDING -> EN_ROUTE, and nothing sets EN_ROUTE. PENDING -> ARRIVED is allowed, and an EN_ROUTE stop gets the button too. - A failed stop whose appointment was IN_PROGRESS fell out of the dispatch pool. The pool's filter is one shared constant and includes it. - A delivery of nothing is refused: that stop could not be done. - The driver's message now says what the server did, not what was asked. - "Transfer it first" compared a delivery's units with the store the order was sold at. A delivery leaves from its store or the store that fulfils for it; the pick page compares with the loading bay's store once scanned. - A count of a place at another store than the scanner's store picker was refused; a place label says its own store. - The receive confirmation was replaced by the PO header at once. - The place lookup shows product numbers, which tell same-named products apart. - Demo seed: committed stock and order-linked transfers now use products on the order, and the open runs carry undelivered orders, so picking and delivery can be shown at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At the owner's direction: the local SonarQube server was not being run, and CodeQL (default setup, a required check that refuses a high or critical alert) covers the analysis. The SonarSource rules worth keeping already run in lint through eslint-plugin-sonarjs, which stays. Gone: docker-compose.sonar.yml, sonar-project.properties, scripts/sonar-set-gate.sh, the sonar:scan script, check:local's Sonar step (now five steps), and the pre-PR hook's Sonar gate block with its sonar-gate-justified marker. The session-start hook no longer queries the server. Skills, the PR template and the runbooks (CI-OPERATIONS, OPERATIONS, WORKFLOW, FRAMEWORK, RULE-PROVENANCE, buyer-drafts) say what runs now. Source comments that cite a Sonar rule number are left: they explain why code is shaped as it is. docs-not-needed: tooling and docs only; the runbooks are updated in this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 4 of the stock-ledger plan (
docs/domains/inventory.md→ "Roadmap", phases 15–20): the warehouse floor on a handheld. Receiving, counting, put-away, picking and loading now happen where the stock is, through the same ledger functions as the desk, and each scan is done once however often a bad connection resends it. Delivery runs can now record what actually happened at a stop: a part delivery, a stop that could not be done, or a return collected on the way.It also removes SonarQube at the owner's direction: the local server was not being run, and CodeQL in CI covers the analysis.
What is in it
/app/scan(lookup, receive, count, move, pick). Each sits behind the permission of the desk screen that does the same job. One resolver reads Holt's place labels (L-<id>), box labels (B-<product>-<box>-<of>), UPCs, consignment barcodes, product numbers and place codes. A retry key (withAction,StockAction) makes each handheld write happen once.inventory.count; posting needsinventory.adjustand moves the difference as ADJUST movements, free stock first. The Oversold report shows the last count's difference. The store-grain count stays as the POS cutover tool.fulfilledQty. The rest stays committed, and a DELIVERY appointment for it goes back in the dispatch pool. The last units are the handover.RETURN_PICKUP), added from the pickup schedule.20261020120000_handheld_floor: additive only.Found while checking it in a browser, fixed here
Trace / Verification
lib/auth/pagePermissions.ts: 11 added lines (the scanner pages and two admin pages).PAGE_ACCESSis read bypagePermissions.test.ts, which checks every page under(dashboard)/apphas an entry. Nothing underlib/adapters,paymentService,secureUploadorautomationschanged.migrate deployinto an empty database, thennpm run setup -- --scale=ci --no-cms, thenprisma migrate diff --from-config-datasource --to-schema --exit-code: "No difference detected".seed:coveragepasses on it. Rebased ontomainafter Returns money: refund to the original payment or to store credit, restocking fees, and a return that books as its own order #33; both migrations apply in order.fulfilledQty;Scan / gate state
npm run check:local, run on the final branch tip after rebasing ontomain(it is five steps now that SonarQube is gone):Deferred (rule 50)
Listed in
docs/domains/inventory.md→ "The handheld floor" and tracked in the owner'sROADMAP.md→ "Handheld floor (left by ledger-plan PR 4)":Test plan
/app/scan/lookup: scan a product number, then a place labelL-<id>./app/scan/pickpick a run's unit to it. The pull list shrinks; "Load the truck" refuses while rows remain./app/dispatch/driver, mark a stop arrived and complete it with one line set to 0. The order stays open, and a new DELIVERY appointment appears in the unassigned pool./app/warehouse/pickups, add a pickup to a run, then complete it on the driver screen. The return reads PICKUP_COMPLETED.🤖 Generated with Claude Code