Skip to content

Sales: the Orders list cannot show which orders are unpaid #769

Description

@mforce

What

The Orders list cannot answer "which orders has nobody paid yet". Add an Outstanding column and
an unpaid filter, so a farm can find the orders owing money instead of opening them one at a time.

The gap, read from the code

The Orders list columns are Reference, Date, Customer, Status, Discount, Total, History
(web/src/routes/SalesPage.tsx:1693). SalesOrderResponse carries TotalMinorUnits and no paid or
outstanding figure (SaleEndpoints.cs:349).

Status cannot stand in for it. SalesOrderStatus is Draft, Confirmed, Shipped, Invoiced, Cancelled, Voided. There is no Paid and nothing in that enum moves when money arrives, so a fully
settled order and one that has paid nothing both read Confirmed.

The data exists, just nowhere useful for this question:

  • GET /sales/{orderId}/payments returns PaidMinorUnits and OutstandingMinorUnits — per order,
    so answering it for a list of 100 means opening 100 orders.
  • GET /customers/balances returns per-customer confirmed total, paid and outstanding, and
    CustomersPage.tsx already renders it.

So "which CUSTOMER owes me money" is answerable today. "Which ORDERS are unpaid" is not.

Not a regression, and not an unmet criterion

#89 scoped exactly two SPA surfaces — "Sales order panel: payments section … outstanding line" and
"Customers page: outstanding balance column" — and shipped both. The Orders list was never in it.
specs/product/GLOSSARY.md:734 documents the same boundary in the present tense: outstanding is
"Shown on the order's payments panel and the Customers page (admins)." The documentation is
accurate; the capability is simply absent. This is unfilled work, not drift.

Nothing in specs/product/specs.md requests it, and Phase 1.5's epic #15 carries no payment items.

Decide before writing code

1 — who sees it. This is the blocking question. #89 made money admin-only end to end and
/customers/balances refuses Workers outright, but the Orders list is AuthPolicies.SalesFlow
(Owner, Manager, Sales and Worker). So this is not additive: it puts money on a screen that
currently shows none, for roles that currently see none.

Three coherent answers, and picking none of them ships an access decision by accident:

2 — column, filter, or both. A per-row badge alone does not solve the stated need: it marks
what is already on screen but does not let you find unpaid orders in a long list. The filter is the
half that answers the question. The list is paged (DefaultPageSize = 100, MaxPageSize = 500,
SaleEndpoints.cs:21-22), so an unpaid filter must be a server-side predicate, never a client
filter over the current page — otherwise it silently means "unpaid among the 100 you happen to be
looking at".

3 — what "unpaid" means. Partial payments are explicitly the normal case per #89. So the filter
needs a stated definition: nothing paid, or anything still outstanding. "Outstanding > 0" is the
useful one for chasing money, but it is a different set from "paid nothing", and the label must say
which. Draft orders have no outstanding concept at all — payments attach to Confirmed orders only.

The constraint that shapes the implementation

GLOSSARY.md:734 is emphatic that these are "server-side sums, never client-aggregated pages".
So the per-order outstanding must be computed in the same query as the page, as a join or lateral
aggregate over non-voided payments — not N follow-up queries, and not summed in the browser. Voided
payments must not count, and the sum must exclude them the same way
ListCustomerBalancesAsync already does; reuse that shape rather than re-deriving it.

Acceptance

  • The Orders list shows each confirmed order's outstanding amount, for the roles decided in (1).
  • A filter returns only orders matching the agreed definition of unpaid, evaluated server-side across
    the whole result set rather than the current page.
  • A fully paid order is visually distinct from one that has paid nothing, and from a partial payment.
  • Voided payments do not reduce the outstanding figure.
  • Draft, Cancelled and Voided orders render no outstanding figure rather than a misleading zero.
  • A role that may not see money sees no amount — and the API refuses it regardless of what the SPA
    renders, per F21: Customer payments + balances — order-attached payments, outstanding per order/customer (admin-only) #89's own split.

Repo rules this touches

Found while reviewing the Sales screen after #727.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiAPI/endpoint layerarea:frontendReact/Vite web clientenhancementNew feature or requestepic-1.5size:MA day or two; migration or a multi-state UI

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions