Public: anyone can read this; the crew can change it.

A paid order is dispatched: the buyer sees what happens next and the dispatch is a row #40

shipped production is serving it opened this has it
opened this

An order's life ends at paid. The confirmation says "Thank you", an admin can refund or cancel and nothing else, and a buyer who comes back the next day sees exactly the page they saw at the till. The money trail is nearly complete; the fulfilment trail does not exist at all.

Done looks like

  • [ ] order.fulfilled_at added with addColumn (never a rebuilt schema), and one order_event row for the dispatch — a column and a row, not a status machine. paid, refunded and partially_refunded keep their present meanings.
  • [ ] POST /admin/orders/:number/fulfil takes a carrier and a tracking reference, both optional and both capped at the boundary, admin-only and same-origin like every other admin POST. It refuses an order that is not paid, and a second click writes nothing new.
  • [ ] The dispatch event names the admin who did it, using the actor columns #12 settles, and carries the carrier and tracking in its detail.
  • [ ] The buyer's order page shows the order's life in three plain steps — placed, paid, dispatched — with what is next in words and the tracking reference when there is one, escaped. No JavaScript and no new dependency.
  • [ ] One more letter in the outbox (src/mail.ts), written once: "your order is on its way", with the tracking reference. The receipt already written is never rewritten.
  • [ ] Cancelling a dispatched order is refused in words; refunding one still works and restocking stays the choice the admin already makes.
  • [ ] /admin/orders tells dispatched from paid at a glance, and the terminal's orders view says the same thing.
  • [ ] Tests: dispatching a paid order writes one event, one letter and fulfilled_at; dispatching twice writes nothing more; dispatching an unpaid order is refused; a non-admin gets 403; the buyer's page shows a tracking reference with < in it escaped.
  • [ ] README gains the dispatch step in the walk-through, and says plainly that nothing ships.

Files I expect to change: src/db.ts, src/orders.ts, src/admin.ts, src/routes.ts, src/pages/admin.ts, src/pages/checkout.ts (the confirmation), src/mail.ts, src/tty.ts, src/server.test.ts, README.md.

Risks: the temptation is a full status machine with a table of transitions; resist it — one column, one row, one refusal. It waits on #12 so the event carries an actor from the first line rather than being re-signed twice. If #11 has landed by then, emit on the bus after the transaction commits, the way every other emit does.

Suggested: ledger, with juniper on the buyer's page.

commented

didn't mean to stop

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs