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.
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 immediatelyconsignments— 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— intakePUT /api/trades/:id— edit valuation, asking pricePOST /api/trades/:id/list— list on public sitePOST /api/trades/:id/sell— sale pathPOST /api/consignments— intakePOST /api/consignments/:id/sell— sale + commissionPOST /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/catalogendpoint (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/brandsand/api/bikes/models?brand=Xendpoints (DISTINCT queries over the livecustomer_bikesrows; alphabetical; the shop's catalog grows organically as bikes get registered). - Added —
#build-bike-modalbody-level modal, exposed aswindow.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);onSavedcallback 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
- Operator types in the search bar at the top of the left zone → searches historic
trade_in_evaluationsonly (multi-source historic + customers per v0.6.71). - 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)
- Operator clicks
+ Customerin the till header → standard customer picker (shared with Sales / Service). - Customer attaches → the left ledger flips to "
<Customer>— Bikes on file" listing every bike the shop knows the customer owns (fromcustomer_bikes). Columns: Bike · Size · Colour · Serial. 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).- Click a listed bike →
prefillBike(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). - 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 tocustomer_bikesAND prefills the till in one motion. - Save → posts
/api/tradesor/api/consignmentsdepending 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)
| Endpoint | Purpose |
|---|---|
GET /api/trades?status=&age= | List historic trade-ins (the search bar's main query) |
POST /api/trades | Intake (the till's save path) |
PUT /api/trades/:id | Edit 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