[C02] Number Capture
Summary
Tenant-scoped, scanner-driven onboarding screen that pairs a manufactured RaceNumber with its physical tag (RFID / chip). A stock operator works from a list of MANUFACTURED numbers for one NumberType, scans the printed number barcode, scans the attached tag barcode, confirms, and the row transitions to IN_STOCK with RaceNumber.tag_id populated. Companion to T06 (file-based onboarding) — the same operational outcome, different input modality.
Actor & Context
Actor: stock operator (typically a tenant-admin assistant working at a workbench with a handheld dual-scanner rig — one trigger reads number, second reads tag). Scope: tenant.
Frequency: bursty — runs through a manufactured batch in one or two sittings after stock arrives, then idle until the next batch lands.
Precondition:
-
User has
TENANT_ADMINpermission (same gate as T02). -
WS3 (US #478a) is in production:
RaceNumber.tag_idFK exists. Without that column the screen has nothing to write to. -
At least one
RaceNumberrow exists inMANUFACTUREDstate for the chosen NumberType (typically created by T06 or by a manual seed script). -
Operator has a handheld scanner that emits the barcode payload as keyboard input followed by
Enter(the standard mode for HID-class scanners).
Entry point: tenant sidebar → Inventory → Onboard → Scan (route /inventory/numbers/onboard/scan per the admin-portal plan). Also reachable from a "Pair tag" button in the row drill-down on T02 when the row is in MANUFACTURED.
Main Flow
-
Pick a NumberType. Operator selects the NumberType the batch belongs to (e.g.
Race-bib-2026,Walking-medal-2026). The screen scopes the working list to that type for the rest of the session. -
Load working list. Page loads
MANUFACTUREDRaceNumbers for that NumberType, filtered to the user’s current tenant, ordered bysequence. List showssequence,number, currenttag_id(empty for fresh stock), and a state pill. -
Pick a mode. Two modes, both keyboard-only, switchable at any time:
-
Single-pair mode: process one row at a time. Each row commits as soon as both scans land. Default mode.
-
Bulk mode: queue multiple pairs in a working buffer (visible as a side rail), then commit the buffer atomically when the operator hits the Commit batch button.
-
-
Number scan. Operator pulls trigger 1 → scanner emits the number’s barcode payload +
Enter. The screen looks up the matchingMANUFACTUREDrow by(numberType, number)from the loaded list. Match → row gets focus, prompt becomes "now scan the tag". No match → AF-1. -
Tag scan. Operator pulls trigger 2 → scanner emits the tag’s barcode payload +
Enter. Tag barcode is captured into the focused row’s pending pair. No backend call yet. -
Confirm. In single-pair mode, the screen requests confirmation (visual flash + the operator hits
Enteragain, or clicks Confirm) and POSTsPOST /api/race-numbers/{id}/tagwith the tag barcode. On 2xx the row’s state pill flips toIN_STOCK,tag_idpopulates, focus advances to the nextMANUFACTUREDrow in sequence. In bulk mode, the pair lands in the working buffer; commit is deferred until Commit batch. -
Commit batch (bulk mode only). Operator reviews the buffer, hits Commit. Backend processes each pair sequentially via the same
POST /api/race-numbers/{id}/tagendpoint; per-pair failures stay in the buffer with an error chip. Successful pairs leave the buffer and reflect in the working list asIN_STOCK. -
Continue. Operator repeats from step 4 until the batch is paired. The KPI strip ("`MANUFACTURED`: 248 / 1000 — paired this session: 752") is the working completion gauge.
Alternative Flows
-
AF-1 — Number scan does not match. Possible causes: wrong NumberType selected, number already paired (no longer
MANUFACTURED), number does not exist. Screen surfaces a non-blocking inline error chip on a "scan log" rail showing the unrecognised payload + suspected reason; focus does not advance. Operator re-scans or clicks the chip to dismiss. -
AF-2 — Tag scan duplicate. The tag barcode already pairs another
RaceNumberrow in this tenant. Server responds 409 with the conflictingraceNumber.id. Screen offers two paths: Skip (clear the pending pair, stay on the row) or Re-pair (release the previous pairing then commit this one). Re-pair callsDELETE /api/race-numbers/{previousId}/tagfollowed by the original POST. Re-pair is logged as two state-log entries on the previous row (per ADR-0001’s single-writer rule) and is irreversible. -
AF-3 — Skip a row. Operator wants to defer a row (e.g. tag missing on the physical number). Hit the Skip control on the focused row → row stays
MANUFACTURED, focus advances to the next row. No backend call. Skipped rows surface in a "skipped this session" tab so the operator can return. -
AF-4 — Undo last commit. In single-pair mode, an Undo control is available for ~5 seconds after the last successful commit. Calls
DELETE /api/race-numbers/{id}/tag. After the grace window the pairing must be reverted via T03 / T04 flows; C02 does not surface a generic un-pair affordance because that would re-purpose this screen for what T02 already does well. -
AF-5 — Re-scan within the same row. Operator scans the tag, realises it’s the wrong tag, scans again before confirming. Latest scan wins; no backend call has happened yet so the buffer simply updates.
-
AF-6 — Bulk commit partial failure. Some pairs in the buffer fail server-side (e.g. tag barcode collision per AF-2). Screen renders a per-pair status next to the buffer entry; failed pairs stay in the buffer with their reason; successful pairs leave. Operator resolves the failed pairs row-by-row.
-
AF-7 — Network drop mid-commit. Single-pair mode renders an inline retry chip on the row; bulk mode keeps the unsubmitted buffer intact. No state-log entries are written for unsubmitted pairs (the SPA never writes optimistically).
-
AF-8 — Tenant scope drift. Defence-in-depth: even though the working list is filtered server-side, the SPA double-checks each scanned row’s
organisationIdmatches the active tenant context before submitting. A mismatch (only possible if the tenant context has been switched in another tab) blocks the commit with a "switch back to <tenant>" notice.
Acceptance Criteria
-
Use-case page authored (this page).
-
Status
design-todo → in-design→handoff-readyafter Claude Design pass and Coordination Backlog resolution. -
:design-url:populated. -
Working list is tenant-scoped; cross-tenant rows never appear.
-
Both single-pair and bulk modes produce identical state-log audit entries (one per pair, written via
RaceNumberAssignmentServiceExper ADR-0001). -
Re-pair (AF-2) writes two state-log entries on the previously paired row (release + dispose-of-pairing).
-
Number scans that don’t match the loaded list never block the operator — they land on the scan log only.
-
Tag-barcode collisions surface a clear Skip / Re-pair choice.
-
All keyboard-only — no mouse interaction required to complete a pair.
API Surface
| Call | Purpose |
|---|---|
|
Load the working list. Tenant scope is implicit via the existing organisation filter on RaceNumberQueryService (Track 1 / Thread B verifies; see Risk #3 in the plan). |
|
Populate the NumberType picker. |
|
Pair a tag barcode to a |
|
Release the current tag pairing. Used by AF-2 (re-pair) and AF-4 (undo). State transitions back to |
|
Drives the KPI strip. Same endpoint Thread B is verifying for T02; reused here filtered by NumberType. |
The pairing endpoint (POST /api/race-numbers/{id}/tag) is a Track 3 backend deliverable — see plan § Track 3 / Backend deliverables. Until it lands, this page documents the target shape; implementation is gated on Track 2 merge.
Out of Scope
-
CSV/XLSX-driven onboarding — T06 (entry bookend) and T07 (exit bookend).
-
Manufacturer-data-file export (the upstream "send sequence to manufacturer" tool) — separate Track 3 screen, not yet authored.
-
Pre-event assignment — E07.
-
Auto-assignment UX — explicitly out of this round per the orchestration plan.
Design Anchors
Design Decisions
-
Scanner-as-keyboard input (2026-05-07). The screen treats both scanners as HID keyboard devices emitting
<payload><Enter>. No browser-level scanner APIs (Web HID, WebUSB) — those are still patchy across the operator’s likely browser stack and the keyboard-emulation mode is the operational standard for the existing equipment. Implication: the screen needs a single focused input element that round-robins between number and tag based on the screen’s pairing state machine; the operator never touches the keyboard outside of pulling triggers. -
Two-scan-then-confirm (2026-05-07). Single-pair mode requires an explicit confirm (
Enteror click) between the tag scan and the backend POST, even though both scans can land in <500 ms. Rationale: scanners occasionally double-fire; without a confirm step a stray double-fire on the tag scanner would commit a malformed pair. The confirm is the brake. Bulk mode skips per-pair confirm in favour of a single batch-level Commit. -
Re-pair as explicit two-step (2026-05-07). When AF-2 (tag collision) fires, the screen does NOT silently overwrite the previous pairing. The operator must consciously choose Re-pair, which releases the previous row first (
DELETE) and then pairs the current row (POST). Two state-log entries land on the previous row. Rationale: silent overwrite is the bug class that allows lost audit trails; explicit two-step is auditable and reversible only via a normal lifecycle flow (T03/T04). -
Skip is non-destructive; Undo is grace-window-only (2026-05-07). Skip never writes anything backend-side; the row stays
MANUFACTURED. Undo is available for ~5 seconds after a successful commit and writes a state-log entry to revert. Beyond the grace window, a pair can only be reversed via T03 (return) or T04 (dispose) — C02 deliberately does not become a generic un-pair tool. -
Tenant-scoping is server-enforced + SPA-double-checked (2026-05-07). The list endpoint filters by tenant server-side (Risk #3 verification under Thread B). The SPA additionally double-checks each scanned row’s
organisationIdmatches the active tenant context before commit (AF-8). Rationale: cross-tab tenant switches can momentarily desync the SPA’s tenant context with the server filter; the SPA-side check protects against the operator continuing to scan into the wrong tenant after switching. -
No per-event scoping (2026-05-07). The list filter does NOT include event scope — manufactured numbers are tenant inventory, not event inventory. Pre-assignment to events is E07's scope.
-
Sequence-ordered working list (2026-05-07). The list is sorted by
sequenceascending so the operator can match the physical pile order (manufacturers ship in sequence). Other sorts are available butsequenceis the on-load default.
Notes
|
Backend dependency: this screen requires US #478a ( |
Resolved Decisions
Q-E1 and Q-E2 (the questions parked during initial authoring) were resolved by Coordination Decision CD-8 in the number/tag UI orchestration on 2026-05-07. Both took the proposed Default leans:
-
Q-E1 (filter-out): the working list shows only
MANUFACTURED AND tag_id IS NULLrows. AF-2 absorbs the explicit re-pair flow when needed. The Main Flow above already encodes this. -
Q-E2 (per-pair POST iteration): bulk mode iterates
POST /api/race-numbers/{id}/tagper pair, concurrency limit 1. Matches the ADR-0001 single-writer pattern; keeps the state-log audit trail granular. No batch endpoint.