[C10] Orders List
Summary
The lists an operator reaches orders through. One component, declared at several anchors, each with a fixed base filter. Every row opens C06 Order Detail, which remains the canonical surface for a single order.
The question these screens exist to answer is who has paid, and on which order. Everything else — totals, composition, the money’s destination — is secondary and may be incomplete while the order domain is still being filled in.
No visual design pass has been run for this screen. The decisions below were taken in the design session of 2026-09-15/20 and are enough to derive a design brief from.
Actor & Context
Actor: event organiser, tenant admin, finance staff. Frequency: daily during registration; heavy during reconciliation and on race week. Precondition: the operator has order-read access in the organisation in context. Entry point: the Orders row in the sidebar, the Orders tab on an event or membership, or a link from a participant’s order chip.
The scopes
Three screens sharing one component, each a fixed filter over the same data:
| Screen | Shows | Reached from |
|---|---|---|
Orders |
Every order in the organisation. |
The Financial sidebar group. |
Event orders |
Orders with at least one line for any event. Narrowed to one event when opened from that event. |
The Events sidebar group; the event’s Orders tab. |
Membership orders |
Orders with at least one line for any membership. Narrowed to one membership type when opened from it. |
The Memberships sidebar group; the membership type’s Orders tab. |
An order carrying both an event line and a membership line appears in all of them. Dismissing the scope chip widens within the family: an event’s orders widen to Event orders, not to every order.
Main Flow
-
Operator opens a scope. The list loads the first page, newest order first.
-
Each row shows: order number, transaction date, buyer, what the order is for, status, total, and — only when it differs from the organisation in context — who sold it. An order from a WordPress store also carries a WooCommerce button.
-
Operator narrows by status, by date range, or by searching order number, buyer or external reference.
-
Operator opens a row, landing on C06 for that order.
-
Browser-back returns to the list with filters, sort and page intact.
Default population
Two groups are hidden by default, each revealed by a labelled toggle carrying its count:
-
Cancelled orders — "Show cancelled (N)".
-
Orders with nothing attached — "Show orders with nothing attached (N)". These have no line item and no participant or membership behind them; production carries thousands, concentrated in one organisation, and they would otherwise be most of that list.
An order is "attached" if it has a line item or a participant or membership pointing at it through the older link, so historical orders are not mistaken for empty ones.
Status
Unpaid means either unpaid or pending. Both are money owed, and any filter, count or chip that separates them misreports what is outstanding. This is the single most important rule on the screen: the whole list exists to answer who has paid.
Payment state comes from the order. The paid flag stored on a participant is not maintained on every path that settles an order and must not be read.
WooCommerce
An order placed through a WordPress store carries a WooCommerce button, shown only when the order’s registration system has an admin order URL configured.
-
Clicking the button opens the order in WooCommerce admin, in a new tab. This is what staff need: the order as the store holds it.
-
Its menu offers Copy payment link, enabled only for an unpaid or pending order that carries a pay link. For any other order the item stays visible but disabled, saying why.
The payment link is for sending to the customer, never for staff to follow — which is why it is copied rather than linked. Opening it lands on the store’s checkout, where staff have nothing to do.
Orders from other systems show their external reference as plain text, without WooCommerce wording.
Alternative Flows
-
AF-1 — No orders in scope. Say so plainly, and say what the scope was, rather than rendering an empty table.
-
AF-2 — A historical event has no orders in its scope. Orders recorded before line items existed cannot be matched to an event. The empty state explains this and links to the organisation-wide list, where they do appear.
-
AF-3 — Totals unavailable. Some orders carry no recorded prices. Their total renders blank with an explanation, never as zero — a zero total asserts a free entry, which is a different fact.
-
AF-4 — Sold by another organisation. The row is visible and the seller is named in its own column, so the operator can see why a total or an action may not be theirs to act on.
-
AF-5 — Permission denied. The scope is not offered in the sidebar at all, rather than offered and refused.
Acceptance Criteria
-
Each scope has its own URL, and the sidebar highlight follows from the path.
-
An order with an event line and a membership line appears in all three scopes.
-
Filtering by unpaid returns orders that are unpaid and orders that are pending.
-
Cancelled and unattached orders are hidden by default, each behind a toggle showing its count.
-
Filters, sort and page survive a refresh and a shared link.
-
No total renders as zero where no price was recorded.
-
An organisation that runs only memberships can reach its orders.
API Surface
| Call | Purpose |
|---|---|
|
The list, with pagination per the collection contract: a bare array and a total-count header, never a page object. Criteria already support status, organisation, dates, order number and external reference. |
Scope filters on |
New. An event id, a membership type id and period id, and has-an-event-line / has-a-membership-line, so each scope is one query. |
|
New. Worked out by the service from the order’s registration system and the order’s external id; absent when the registration system has no template, which is what hides the button. Computed server-side so that neither the template nor the rest of the registration system has to be sent to the browser. |
Admin order URL template on |
New column. A full URL containing an |
|
New and opt-in, default off, so other consumers of this endpoint are unaffected. Supplies the total and a summary of what the order is for. Without it the list has no total, which is why the first delivery slice shows none. |
Out of Scope
-
Order actions. Mark paid, refund and cancel live on C06. A list row opens the order; it never mutates one.
-
Ledger figures. Recording a payment is transaction logging; posting it is a separate concern, and no financial statement is derived here.
-
Bulk operations. No multi-select, no bulk status change. Each order is opened on its own.
-
Creating an order. There is no "new order" affordance on this screen.
Design Decisions
-
One component, several anchors (2026-09-20). Three screens sharing one component and differing only by base filter, rather than one screen with a kind selector. Two sidebar rows pointing at one route would make the active-highlight ambiguous, which is the defect the navigation rework removed; and a scope held in a query parameter is the form that rework deliberately canonicalises away.
-
Widening stays within the family (2026-09-20). An event’s orders widen to Event orders. Jumping straight to every order would discard the operator’s context in one click.
-
Scoping is by line item (2026-09-20). An order belongs to an event because a line says so. Orders predating line items stay reachable at organisation level and in the detail screen, and are excluded from event and membership scope rather than being guessed at.
-
Cancelled and unattached hidden by default (2026-09-20). Both are real records worth keeping and neither is what an operator came to see. The count on each toggle is what stops hiding from becoming concealment.
-
Blank beats zero (2026-09-20). Where no price was recorded, the total is blank with a reason. Rendering zero would assert something the data does not say.
-
The seller column appears only when it differs (2026-09-20). Most orders are sold by the organisation viewing them; a column that repeats the obvious on every row earns nothing.
-
The WooCommerce admin link is a per-store template (2026-09-22). Where WordPress is installed and how WooCommerce stores its orders vary by site, and independently of each other. Tour de Worcester is a Bedrock install, with WordPress under
/wpand orders in WooCommerce’s order tables; Western Cape Schools Cycling and Helderberg are standard installs still storing orders as posts. A store can also change how it stores orders without its integration changing. A template records the answer per store instead of deriving it from the integration type. Typing it by hand is error-prone, so a guided builder is planned as later work. -
The payment link is copied, not linked (2026-09-22). Staff share it with a customer who has yet to pay; they never pay on a customer’s behalf.
Notes
|
Delivery is staged. A first slice gives an organisation-wide list with no totals, built entirely on what the API already returns, because the question worth answering soonest is who has paid and on which order. A second slice adds the event and membership scopes and the WooCommerce button. The summary enrichment and the seller column follow with the wider order work. |
The chip on the participant list is the other way into an order and is specified with that screen, not here. Both converge on C06.