Order management
An HPOS-grade operator cockpit for orders — workspace, returns/RMA, pick-list PDFs, and AI next-best-action. All flag-gated, default-off.
Every Cartwright shop already stored orders in proper relational tables (not a posts/meta hack), so the order data layer was never the bottleneck — the operator experience was. The order-management set turns /admin/ordrer from a flat list into a real back office: a scalable Orders workspace, a per-order lifecycle, admin-initiated returns, printable pick lists, and rule-based next-best-action suggestions.
Everything ships behind four default-off, ecommerce-gated runtime flags. An upgrade behaves exactly as before until you flip a flag, website-mode shops never mount any of it, and with orderWorkspace off the legacy order table is unchanged.
"HPOS" is a nod to WooCommerce's High-Performance Order Storage. Cartwright orders were already in dedicated, indexed tables — this release brings the operator surface up to that bar: server-side filtering, cursor pagination, bulk actions, and a proper order-notes/timeline model.
Order workspace
brand.features.orderWorkspace — the cockpit.
- Status tabs over a 12-status lifecycle. The nine existing statuses are kept verbatim; three admin-only ones (
processing,delivered,completed) are added. A pure state machine governs which transitions an operator may make — illegal moves are rejected with a reason, and the Stripe webhook keeps writing payment/refund/dispute statuses unguarded (they're facts about the world, not operator intent). - Server-side search + cursor pagination over indexed columns (email / order id / date range), so the screen stays fast at thousands of orders.
- Bulk status actions with per-order skip reporting (an illegal transition in the batch is skipped and reported, not silently dropped).
- Exception flags computed per row: delayed shipment, low stock on a line, or "needs attention" (flagged-review / disputed).
- Per-order timeline + internal notes in one model (system entries for status changes, refunds, tracking, emails; private operator notes interleaved).
- Tracking entry, resend-confirmation, and send-shipping-notification — the carrier/number/URL fields were always on the
Order; now they're editable and drive a customer email. - Manual refund — issues a Stripe refund from the order; the
charge.refundedwebhook stays the single writer of refund status (and now resolves the order via the payment intent, so dashboard refunds finalize too).
Fulfillment & pick lists
brand.features.fulfillmentPdf (needs orderWorkspace).
A print-friendly packing-slip / pick-list route renders as @media print HTML — the operator hits ⌘P → "Save as PDF". No PDF dependency, nothing to cold-start on serverless. A one-click "create fulfillment" reuses the existing supplier-routing so multi-supplier orders split into per-supplier slips.
Returns / RMA
brand.features.returns (needs orderWorkspace). Admin-initiated — no customer portal.
Create a return against an order's lines with a reason, then move it through requested → approved → received → refunded (or rejected). Receiving a return restocks the items idempotently — a restocked flag inside the transaction guarantees stock increments exactly once even on a double-click or retry. Refunds reuse the same Stripe path as the workspace.
Refund and restock are deliberately decoupled: a refund returns money (webhook-finalized), a return returns money and stock. This avoids the un-keyable "did the webhook already restock?" race.
AI next-best-action
brand.features.orderAi (needs orderWorkspace).
A deterministic rule engine reads each order's state and surfaces a ranked list of the next sensible action — ship now (overdue), follow up on delivery, review a flagged payment, submit dispute evidence, process a return — as chips that deep-link to the relevant control. It's pure and free (no LLM call), so it works in every shop; an optional model-backed "ask AI about this order" layer can sit on top when a provider key is present.
Enabling it
All four flags are off by default. Flip them per shop in /admin/features (they're runtime-toggleable once the shop is in webshop mode), or set them in brand.config.ts. Run pnpm db:push once to add the additive OrderNote / Return / ReturnItem tables and the nullable billing-address columns — lossless, safe to apply to a live database before deploying.
Admin panels
The admin routes that run a Cartwright shop and what each one does.
In-place AI editing
Edit your live storefront by clicking it — admin toggles edit mode, clicks a copy element, writes a note, and an AI proposes new copy with a before/after diff before it applies. Flag-gated, default-off.