All docs

Receiving & box scanning

Box-level scanning is how a small operation gets a real WMS workflow without a real WMS. Every received unit is traceable to a specific box, and your warehouse team picks from one box at a time so the next box's labels stay on it until you need them.

What "box-level" means

When you receive a PO, the app generates one receipt box per pre-set unit count. A 5,000-unit PO with 100 units per box gives you 50 boxes. Each box gets:

  • A unique workspace-scoped barcode (e.g. K9-42)
  • A printed label with the SKU, qty, and PO reference
  • A state: UNOPENEDOPENDEPLETED, with MISSING and DAMAGED

exception states

  • An immutable labeled quantity and a separate ledger quantity remaining

Boxes give your warehouse team a physical handle on the inventory: which box is open, which one to pull from next, which ones are still sealed.

Generating boxes

When you click Receive on a PO, the receipt page asks for units per box. The app then creates the right number of boxes per receipt line. You can edit the count if your supplier's pack-out is non-standard.

Boxes are unique per workspace, not globally — your K9-42 and another tenant's K9-42 won't collide.

Printing labels

After receiving, open the receipt detail page and click Print labels. Two sizes:

  • Large (4×6") — portrait, default. Works on a thermal label printer
  • Small (3×2") — landscape, toggleable

Four design variants: classic, editorial, bold, minimal. Tack ?variant=bold (or any other) onto the URL to switch. Real Code-128 barcodes render via bwip-js; they scan reliably on phone cameras and 1D scanners.

A note on print stylesheets: most browsers don't print background colors by default. The labels use borders instead of background fills so they print cleanly without you needing to enable "Background graphics" in the print dialog.

Scanning to deplete

Open Receiving in the sidebar. Two paths to enter a code:

  • Text input — type the box code, hit enter
  • Camera scanner — point your phone at the barcode (uses the device's camera via @zxing/browser)

Scanning a code:

  • If the box is UNOPENED: marks it OPEN and depletes any previously open box for the same SKU (one open box per SKU is the rule)
  • If the box is OPEN: nothing happens — already in use
  • If the box is DEPLETED, MISSING, or DAMAGED: offers a deliberate reopen

so an accidental state can be reversed with its original cost layers

The "Mark depleted" checkbox lets you flag a box as fully consumed in one shot — useful for gift events, single-customer pulls, or cases where you don't open a fresh box.

Box state and inventory

Every inventory-carrying product has exactly one depletion writer:

  • BOX mode — marking a carton depleted removes only the quantity that carton

still represents in the ledger. Missing and damaged cartons use distinct shrinkage movement types. Channel sales are a read-only operational overlay.

  • SALES mode — channel/POS sales write outbound SALE movements. Carton

states remain operational and never create a second inventory movement.

unitsPerBox remains the label quantity. ledgerQtyRemaining starts at that quantity, can be anchored to an exact partial-carton cycle count, and becomes zero in a terminal state. If a 445-unit open carton is counted at 300, the count corrects 145 units and its later depletion removes only the remaining 300.

All scanner, detail, bulk, undo, and cycle-count state changes route through one transition service so state, inventory, cost-layer reversals, and audit history stay in sync.

Cycle-count evidence

A cycle count never changes live carton state while the warehouse operator is counting. It stages one mutually exclusive observation for every expected carton: sealed, open with an exact remainder, empty, missing, or damaged. An Owner/Admin reviews the evidence before one atomic posting applies carton corrections and any residual unit variance.

Phone camera + HTTPS

iOS Safari blocks getUserMedia on plain http:// (except on localhost). For phone-camera testing during dev, route through an ngrok HTTPS tunnel. In production this is a non-issue — the deployed app is on HTTPS by default.