Skip to main content

Backorder → Bucket flow

The old shop workflow: cashier rings a bike, product's out of stock, cart won't let them add the line, cashier walks the customer to Purchasing, someone opens a special-order form, the customer waits. The v0.7.190 flow: cashier rings, line lands in the cart red, Charge auto-orders the shortfall. Zero-stock lines are no longer a blocking popup — they're just a line the register handles.

One flow, three modules touching one substrate.

Shippedv0.7.190 (ring-through) → v0.7.204 (final polish)
Substrateinventory_order_queue (the Bucket) + core/movements.ts allowNegative flag
Retired/reorder endpoint (v0.7.190) — the manual reorder form is unnecessary now that Charge auto-buckets

The Sales side

Zero-stock line rings through. The old "can't add — out of stock" popup is gone. The line adds with a red backorder N annotation on the line; the cart totals include it; the tax math includes it. The operator has the customer's attention, not the "out of stock" popup's.

Charge auto-buckets the shortfall. On Charge, for every cart line with qty > on_hand, the shortfall (qty − on_hand) auto-inserts into inventory_order_queue — earmarked to the customer if one's attached, or for Stock if not. The Sales transaction is posted as a Special Order (invoice status Awaiting order); the Sales ledger row will track the Bucket line → PO line → Ordered → Partially received → Ready for pickup lifecycle downstream.

Out-of-stock pill = Adjust actual stock. If the cashier believes there IS stock — miscount, misshelved, moved to the back room — the OOS pill on the cart line is now an inline Adjust actual stock stepper (v0.7.196/.201). Incrementing commits live to inventory.quantity_on_hand via /count (debounced), an "Inventory Adjusted" popup confirms, the cart line's red goes away when on-hand rises above what's needed, and no line ever hits the Bucket. This is the corrective path — the wrong-count case — routed through the cart instead of the operator swapping to Inventory to fix the number.

The Inventory side

The /count endpoint (v0.7.196) writes one immutable inventory_movements row through core/movements.ts — same code path a manual adjust from the Inventory tab uses. Actor / reason are captured; the audit chain narrates it as "Marcus adjusted Trek Domane 4 SL (56) on-hand: 0 → 1."

The allowNegative flag on the movement engine is what makes zero-stock ring-through legal — Sales posts with allowNegative: true on backorder lines so the movement engine doesn't reject the debit. On-hand can go temporarily negative when a sale posts before the reorder receipt (rare — usually the Bucket → PO flow beats the sale, but the invariant holds either way).

Bulk items are real SKUs (v0.7.136, migration 039). The product_variants.is_bulk flag distinguishes bulk items (grease, chain lube, small parts) from bike-serial variants; both live in the same catalog and both route through the same movement engine. The Sales bulk-item palette reads is_bulk = 1.

The Purchases side

"Awaiting order · for <Customer>". The Bucket row (v0.7.191) — the pre-PO staging where reorder-worthy lines accumulate — now shows who each line is for:

  • "for Stock" — neutral chip, means the ring didn't have a customer attached
  • "for <Customer>" — blue chip, means a customer's earmarked; when the PO's cut and the goods arrive, that customer's the pickup

The Bucket /bucket GET returns customer_id, special_order_transaction_id, and customers.display_name so the chip renders without a second round-trip. Migration 033 (v0.7.86) already carried the special_order transaction → Bucket line → PO line linkage; v0.7.191 surfaces it in the operator UI.

Vocabulary — Bucket = "Awaiting order" (the reorder hasn't been ordered from a vendor yet); PO cut = "Ordered" (vendor has it). The Sales ledger's "Awaiting order" status is the mirror of the Bucket row upstream (v0.7.132).

What the operator sees

A cashier ringing a $2,411 Trek Domane on which the shop has 0 on hand:

  1. Line adds red with backorder 1
  2. Cart totals reflect it; no popup
  3. Cashier tries Adjust actual stock — no, definitely 0
  4. Attach customer — Joe Customer
  5. Charge — customer pays $500 layaway; invoice status Awaiting order
  6. On the Purchases tab (any station, next refresh), the Bucket has a new "Trek Domane 4 SL (56) · Awaiting order · for Joe Customer" row
  7. The buyer batches it into a Trek vendor PO, cuts the PO; Sales ledger row flips to Ordered
  8. Trek ships; receiving marks the Bucket line received; Sales ledger flips to Ready for pickup
  9. Joe comes in, cashier resumes the special order into the Till, takes the balance, invoice closes as Sold

Every state is one row on one substrate — no email chain, no whiteboard, no "did we order that yet?"

Retired: /reorder

Pre-v0.7.190 there was a manual /reorder endpoint — a form where the operator picked SKU + qty to add to the Bucket. It's gone. Every Bucket entry now originates from either a Sales ring-through (auto-bucket at Charge) or the Purchases-tab Reorder dashboard's explicit add (BIKE.L2-0221 was the manual form's designation, retired v0.7.190).

See also