Skip to main content

Slice 8 — Trade-In & Consignment

Used bikes coming into the shop, either as trade-in (immediate ownership transfer for credit) or consignment (shop sells on behalf of original owner, takes commission).

Status: Trade-in workflow live and shipping (v0.6.69 → v0.6.77). Consignment workflow schema done; sale + payout still TBD.

Drafted from planning · v0.1

Scope

  • trade_ins, consignments (separate tables; different lifecycles)
  • Intake form (photos, condition assessment, asking price, owner info)
  • Valuation aid (similar bikes sold recently)
  • Listing on the public site
  • Sale flow (creates a transactions)
  • Consignment payout (separate transaction back to original owner)

Schema

  • trade_ins — owner is the customer being credited; bike becomes shop inventory immediately
  • consignments — owner retains title; shop holds and sells; commission applied at sale

Both reference customer_bikes for the bike record.

Endpoints

  • GET /api/trades — list (filter by status, age)
  • POST /api/trades — intake
  • PUT /api/trades/:id — edit valuation, asking price
  • POST /api/trades/:id/list — list on public site
  • POST /api/trades/:id/sell — sale path
  • POST /api/consignments — intake
  • POST /api/consignments/:id/sell — sale + commission
  • POST /api/consignments/:id/payout — owner payout

Trades tab — built end-to-end (v0.6.69 → v0.6.77)

Trades is now a live working surface that mirrors the cross-tab .till-left / .till-right shape. Status changes from earlier draft: the "Bike Index lookup" path was dropped (Bike Index is a stolen-bike registry, not an OEM catalog; can't deliver spec data and the registry records were noisy). Replaced with a shop-grown brand/model catalog built from the customer-bike intake stream, plus a build-bike modal that adds new brand/model values to the catalog on the fly.

Architecture changes from v0.1 draft

  • Removed/api/tradein/catalog endpoint (Bike Index proxy), 24 h cache, search-bar mode toggle (Historic ↔ Bike Index), catalog rendering branch in the result dropdown, "Look up" chip strip (Google / Pinkbike / Bike Index source links).
  • Added/api/bikes/brands and /api/bikes/models?brand=X endpoints (DISTINCT queries over the live customer_bikes rows; alphabetical; the shop's catalog grows organically as bikes get registered).
  • Added#build-bike-modal body-level modal, exposed as window.helmOpenBuildBike({customerId, customerName, onSaved}). Brand + model comboboxes backed by <datalist> from the two endpoints; free-text typing in either field adds that value to the shop's catalog the next time the dropdowns load. Year, frame size, colour, serial, notes round out the form. Save POSTs /api/customers/:id/bikes (existing endpoint); onSaved callback fires with the new bike so callers can prefill their till + refresh their bikes-on-file list. Same modal is reused from Service drop-off intake (see slice 4 → Bike field is a picker).

Workflow today

Trades tab follows the unified .till-left / .till-right two-zone pattern (Sales, Service, Products, Customers all share it). The Trades surface is the fifth working tab to carry the same anatomy.

Workflow A — search by historic trade-in

  1. Operator types in the search bar at the top of the left zone → searches historic trade_in_evaluations only (multi-source historic + customers per v0.6.71).
  2. Click a result → past trade-in detail opens in the till; operator can re-list, adjust pricing, or use it as a comparable for a new intake.

Workflow B — customer-first (the canonical path for new intakes; v0.6.77)

  1. Operator clicks + Customer in the till header → standard customer picker (shared with Sales / Service).
  2. Customer attaches → the left ledger flips to "<Customer> — Bikes on file" listing every bike the shop knows the customer owns (from customer_bikes). Columns: Bike · Size · Colour · Serial.
  3. setLedgerTableMode('bikes' | 'listed') swaps the ledger thead between the two views; the till body itself stays purely the intake form (no bike-picker INSIDE the till — that was the v0.6.69 → v0.6.77 simplification).
  4. Click a listed bikeprefillBike(id) fires → till intake fields (brand, model, year, frame size, colour, serial) pre-populate with the picked bike's identity. Operator fills in condition notes, asking price, photos, disposition (Trade-in vs Consignment), commission split (Consignment).
  5. Or click + Build new bike (button in the ledger header, visible only when a customer is attached) → opens the build-bike modal → save adds the bike to customer_bikes AND prefills the till in one motion.
  6. Save → posts /api/trades or /api/consignments depending on disposition; till resets; ledger refreshes.

Workflow C — walk-in / unknown bike

+ New customer in the till footer → minimal customer-create → bike auto-attaches via build-bike modal → continue as Workflow B.

Detach (× on the customer pill, or load a different past trade) → ledger flips back to 'listed' mode and reloads the currently-listed trades.

Endpoints live today (v0.6.77)

EndpointPurpose
GET /api/trades?status=&age=List historic trade-ins (the search bar's main query)
POST /api/tradesIntake (the till's save path)
PUT /api/trades/:idEdit valuation + asking price
GET /api/bikes/brands?q=DISTINCT brand values for the build-bike modal datalist
GET /api/bikes/models?brand=DISTINCT models for the picked brand

Consignment endpoints (POST /api/consignments, /api/consignments/:id/sell, /api/consignments/:id/payout) are still planned — the disposition picker on the till routes to either path but only the trade-in branch is currently wired end-to-end. Consignment commission split + owner payout flow still TBD.

Removed: per-screen color themes / 🎨 picker (v0.6.79 → v0.6.82)

A brief sub-arc: v0.6.79 added per-screen accent themes + a paint-can picker in Adjust mode (operators could colour their own tabs). v0.6.80 reverted that — too noisy. v0.6.81 / v0.6.82 settled on ROYGBIV accents in nav order (Owner red → Sales orange → Service yellow → Products green → Customers blue → eComm indigo → Trades violet) on the page header line + active tab — same idea, narrower scope, not operator-customisable. The Service tab's yellow ROYGBIV slot is also what the "Service" parked-sale badge uses on the Sales ledger (see slice 5).

Status now

  • Trade-in workflow — live and shipping at Swicked
  • Build-bike modal — live; the catalog grows as operators use it
  • Bikes-on-file ledger view — live
  • Consignment workflow — schema and intake exist; sale + commission + payout still TBD
  • Public-site listing flow — still TBD (depends on the public-site Worker)

What's not yet built

Acceptance criteria

  • Operator can intake a trade with photos, valuation, owner credit
  • Consignment intake records owner contact + percentage agreement
  • Listings flow to the public site (slice that depends on the public-site Worker)
  • Sale flow handles both trade and consignment correctly (consignment computes commission)
  • Owner payout for consignment is a tracked transaction, not just a manual cheque

See also