One funnel, one adjacency table
Every order is one record moving through a single canonical status sequence — intake, pending dispatch, assigned, accepted, collected, completed — with explicit leakage branches for service hold, cancelled, past date-of-service and rejected.
The legality of every move lives in one pure module with no database dependency: an adjacency table of which statuses may follow which, a map of which timestamp column each transition stamps, and a map of which timestamps a transition clears. That last map encodes a real business reversal — completed is not fully terminal, and moving it to rejected or cancelled nulls the collection and completion timestamps so a voided collection stops counting as collected and drops off the laboratory audit report.
A sibling module generalises the same pattern to three other entities that originally had no enforced lifecycle at all. Each carries a comment naming the specific QA finding that made it necessary: a state resurrecting itself, orphaned records, an urgent request page coming back from the dead. Every mutating route calls one guard that turns an illegal move into a conflict response — deliberately a client error rather than a server error, because the field application's offline queue treats server errors as retryable.
