Status Logic v5

How a watch's status is decided — automatically and consistently — across your Inventory, your Deals, and your Invoices. One source of truth, three views, never in conflict.

Plain-language guide Updated 2026-05-21 Server + Web
The Idea

Money decides the status — not a dropdown

Every watch's status, everywhere you look, is worked out from three simple facts. Nobody picks a status by hand that could disagree with the money. The invoice is where things actually happen (a payment, a return, a void, a trade); those actions move the ledger; the ledger then sets the status on the watch and the deal. One direction of truth.

1 · Is it reserved?

A watch is being held for a specific buyer — but no money has come in yet.

2 · Has any money been paid?

Add up the payments against the sale. Even one dollar changes everything.

3 · Is it out on memo?

The watch has been loaned out to a known buyer for consideration.

Two rules we never break

INV-1 · One ledger, three views.

Inventory status, Deal status, and Invoice status are three views of the same sale. They are recalculated together, in a single step, every time money changes. It is impossible for the watch to read "Sold" while its deal still says "Reserved."

INV-2 · Every held item has a contact.

Reserving a watch or sending it on memo requires a buyer to be attached — no anonymous holds. The same contact then shows on the inventory card, the deal card, and the invoice. In Stock is the only status with no contact.

One Ledger, Three Views

The same sale, shown three ways

There is one set of money records (the ledger). The Inventory page, the Deals pipeline, and the Invoice are simply three windows onto it. Change the money once, and all three update at the same time.

flowchart TB L["💰 THE LEDGER
payment records · agreed price · reserve & memo intent"]:::ledger L --> I["📦 INVENTORY VIEW
In Stock · Reserved · Sold · On Memo"]:::view L --> D["🤝 DEAL VIEW
Reserved · Partially Paid · Paid"]:::view L --> V["🧾 INVOICE VIEW
Draft · Sent · Partially Paid · Paid in Full · Returned · Void"]:::view classDef ledger fill:#000,stroke:#c9a96e,stroke-width:2px,color:#fff; classDef view fill:#fff,stroke:#c9a96e,stroke-width:1.5px,color:#15130f;
The bug this kills: today the three statuses are stored separately, so a deposit could update one and leave the others stale (watch says "Sold," deal still says "Reserved"). In v5 they are always derived together, so they can never drift apart.

The status words, finalized

📦 Inventory · 4

In Stock Reserved Sold On Memo

🤝 Deal · 3

Reserved Partially Paid Paid

🧾 Invoice · display

Draft Sent Partially Paid Paid in Full Returned Void

Retired words: Inventory Listed / Consigned / Incoming are gone (Consigned becomes an attribute on the item; the rest become In Stock). Deal Inquiry / Negotiating / Deposit Received / Shipped / Closed / Cancelled are gone — deals are now commitment-only.

The Status Table

Every situation, side by side

This is the master spec. Pick the situation on the left, and read across to see exactly what each view shows.

Situation📦 Inventory🤝 Deal🧾 Invoice
UncommittedIn Stocknone
Held, $0 paidReservedReservednone — no invoice, no prompt
Any payment, less than fullSoldPartially PaidPartially Paid
Paid in fullSoldPaidPaid in Full
Out on memoOn MemononeMemo invoice (optional)
ReturnedIn Stock (restock)demoted / closedReturned
Voided (before any payment)In StockremovedVoid
Hard rule: any payment greater than zero against a sale makes the watch Sold and the deal Partially Paid (or Paid). Reserved is only the zero-payment state.
Reserved = no invoice. Reserving a watch creates no invoice and shows no "create invoice?" prompt. An invoice only appears when money moves — or when you explicitly create one yourself.
Watch Lifecycle

The journey of one watch

A single watch moves between just four inventory states. The arrows show every move that's allowed, and what triggers it.

stateDiagram-v2 direction LR [*] --> InStock InStock: 📦 In Stock Reserved: 🔖 Reserved Sold: ✅ Sold OnMemo: 📤 On Memo InStock --> Reserved: Reserve (buyer required) InStock --> OnMemo: Send on memo (buyer required) InStock --> Sold: Record payment Reserved --> Sold: Record payment Reserved --> InStock: Release hold OnMemo --> Sold: Sell this memo piece OnMemo --> InStock: Return to stock Sold --> InStock: Return / Refund / Void Sold --> [*]: Stays sold

What each state can do

  • In Stock — reserve, send on memo, mark sold, adjust price
  • Reserved — take payment (becomes Sold), release, adjust price. No memo.
  • Sold — return, refund, or void via the invoice only. No reserve/memo.
  • On Memo — convert to a sale, or return to stock. No reserve.

The contact gate

Both Reserve and Send on memo require a buyer to be attached first. If none is chosen, WatchFlow opens a contact picker — there is no "skip" option. That same buyer then appears identically on the inventory card, the deal card, and any invoice.

Walkthroughs

Real scenarios, step by step

Three common situations, traced through all three views so you can see them stay in sync.

1 Deposit on a $100k watch
  • Watch sits as In Stock — no buyer, no deal, no invoice.
  • You reserve it for a buyer. Inventory Reserved, Deal Reserved, still no invoice.
  • Buyer pays a $20k deposit. Inventory flips to Sold, Deal becomes Partially Paid, an invoice appears as Partially Paid.
  • Buyer pays the remaining $80k. Deal Paid, Invoice Paid in Full. Inventory stays Sold.
2 Out on memo, then sold
  • You send the watch on memo to a known buyer (buyer required). Inventory On Memo, no deal. Optionally create a memo invoice.
  • Buyer decides to keep it. You choose "Sell this memo piece." the memo invoice becomes a sales invoice and the normal payment flow opens.
  • Payment is recorded. Inventory On Memo becomes Sold; a deal is created at Partially Paid or Paid.
3 Part cash, part trade-in
  • On a $100k invoice, the buyer pays $50k cash. Deal Partially Paid, Inventory Sold.
  • You record a trade-in: the traded watch's name and an agreed value of $50k (required). a trade payment of $50k is logged, and a visible trade-in line is added to the invoice.
  • $50k cash + $50k trade = $100k. Invoice Paid in Full, Deal Paid.
  • Optionally tick "Add to inventory now" to create the traded watch as a new In Stock item — or add it later. Either way the sale is never blocked.
Migration

What happens to existing data

This is a relabeling pass only. No payment, invoice, or money record is ever changed or deleted — payments are the truth. Old status words are simply remapped to the new ones, then everything is reconciled against the ledger.

📦 Inventory remap

  • Listed In Stock
  • Incoming In Stock
  • Consigned In Stock + acquisition type "consignment"

🤝 Deal remap

  • Deposit Received Partially Paid
  • Shipped / Closed Paid
  • Any stage with a payment derived from the money
  • Inquiry / Negotiating with a watch + $0 Reserved
  • Empty leads (Inquiry/Negotiating, $0, no watch, no payments) deleted
Safety guard: a deal or inventory link is only ever deleted when it has zero payment history. Anything touched by money is preserved untouched. The migration is re-runnable and is tested on the dev copy of the database before it ever runs on the live machine.
Build Plan

How it ships — five phases

Each phase is its own commit, tested and verified before the next. Server first, then the screens, then the special flows.

1

Server truth

Collapse deal stages to 3 and inventory to 4, flip the "any payment = Sold" rule, write the migration and reconcile pass. No UI yet.

2

Web rendering

Inventory's 4-status UI, the 3-column deals pipeline, and the new invoice display labels — pure presentation against the new server truth.

3

Memo lifecycle

Create-a-memo-invoice on send, and the "Sell this memo piece" path that converts a memo into a sale.

4

Trade-in

The capture box (name + value required), the visible trade-in line on the invoice, and add-now / add-later inventory creation.

5

Verification

Walk every row of the scenario matrix against the new model, fix any drift, and lock the status columns.

Scope of this pass: server + web only. iOS is deliberately deferred until the web model is frozen, because the app reads these exact status words. Reports wiring is a separate follow-up.