Skip to content

feat(tracking): structured tracking-url QR codes, Code 128 barcodes, backward-compatible scan resolver - #356

Merged
roncodes merged 3 commits into
release/v0.6.72from
feature/structured-tracking-codes
Oct 7, 2026
Merged

roncodes merged 3 commits into
release/v0.6.72from
feature/structured-tracking-codes

Conversation

@roncodes

@roncodes roncodes commented Oct 6, 2026

Copy link
Copy Markdown
Member

Tracking-number QR codes and barcodes were generated from the owner's bare uuid. This changes them to carry a structured, versioned payload, and makes every scan endpoint accept both the new codes and the uuid codes already printed on parcels.

New code content

QR (medium error correction, up from low): a tracking url.

https://<console host>/track-order?order=<tracking number>&r=<owner public_id>&v=1
  • A phone camera opens it as the customer tracking page. The navigator app and other scanners read order, r and v.
  • It holds only the tracking number and the owner's public id. No uuids, addresses, names or company.
  • The owner's type is its public id prefix (order_, waypoint_, entity_, place_), so it isn't repeated.
  • It isn't signed. A label can be photocopied whatever it carries, so authorization stays with the endpoint that acts on the scan.
  • The route is held in two constants, TrackingCode::PATH and TRACKING_PARAM. The public tracking page is being rebuilt; when its route lands, those two change and nothing else. Parsing never depends on host or path, so codes printed in the meantime keep resolving.

Barcode: Code 128 (was PDF417) carrying the bare tracking number, matching the text printed beneath it. Warehouse handhelds type what they scan into a field, so a url there would be noise.

Existing records are left alone. Their stored images keep the uuid codes, and every resolver below still accepts those.

Resolving scans

All parsing goes through the new Support\TrackingCode. It accepts:

  1. A tracking url, from any host. The tracking number comes from order (or tn), or from the last path segment for a future /track/<number> route.
  2. A bare owner uuid: legacy labels.
  3. A bare value: a tracking number from the barcode or typed by hand, or a public id.

The two endpoints:

  • POST orders/{id}/capture-qr/{subject?} uses TrackingCode::matches().
    • A bare uuid still matches by exact equality, as before.
    • A url is decided by its owner public id. An owner can hold more than one tracking number, since the API issues extra ones.
    • With no owner in the code, the tracking number must be the subject's.
  • POST tracking-numbers/from-qr uses TrackingNumber::findOwnerByCode(). Two behavior changes:
    • It is now scoped to the session's company. It used to match a uuid across all tenants.
    • It resolves waypoint and place owners, not only orders and entities.
    • A url whose tracking number and owner disagree, or that names an unknown tracking number, resolves to nothing rather than falling back to the half that matches.

Other changes

  • qr_code_content on the TrackingNumber resource is now present in every environment and holds the url. It used to be debug-only because it exposed the owner uuid. Webhook payloads are unchanged.
  • Labels (order, waypoint, entity) print the QR as a fixed 100px square. The 110×100 box with object-fit: cover used to crop it in browsers and stretch it in dompdf. The barcode is capped at 360×90 with its aspect ratio kept, so reprints of older PDF417 labels still fit.
  • Waypoint and Entity raw inserts pass the owner's public id along so their codes name it.
  • The Eloquent create path (TrackingNumberObserver) generates the same codes.

Navigator app

No change is needed for this to work: it sends the raw scanned value to capture-qr, which now understands every format. A follow-up list for fleetbase/navigator-app is in a comment on this PR.

Tests

Pest, in the package's existing seam and SQLite style. Verified through CI only.

  • TrackingCodeTest: generation, parsing of every format including hostile or partial urls, and subject matching.
  • TrackingNumberControllerHelpersTest: the resolver against SQLite, covering company scoping, disagreeing identifiers, unknown numbers and unmapped prefixes.
  • Insert path, observer, both controllers and the resource updated off the uuid.

QR codes now carry a versioned tracking url naming the tracking number and
the owner's public id (https://<console>/track-order?order=<tn>&r=<public_id>&v=1)
instead of the owner's bare uuid, so a phone camera opens the tracking page
while scanners read the parameters. The barcode is now Code 128 with the bare
tracking number, matching the text printed beneath it.

capture-qr and tracking-numbers/from-qr resolve scans through one parser that
accepts the new url, a bare tracking number or public id, and the bare owner
uuid printed on labels already in circulation. from-qr is now scoped to the
session's company and resolves waypoint and place owners too.

qr_code_content is published in every environment: it no longer exposes a uuid.
Labels print the QR as a fixed square instead of cropping it with object-fit.
…can resolver

- TrackingCode: generation, parsing of every format in circulation (legacy uuid,
  tracking url from any host, bare tracking number or public id, hostile urls),
  and subject matching for capture-qr
- TrackingNumber::findOwnerByCode against SQLite: company scoping, disagreeing
  url identifiers, unknown numbers and unmapped prefixes
- insert path, observer and resource expectations moved off the owner uuid
capture-qr reads `code` straight from the request. The navigator v3 branch
sends the scanner's whole result object; the old strict comparison answered
400, and the typed matcher would have thrown. Non-strings now match nothing.
@roncodes
roncodes changed the base branch from main to release/v0.6.72 October 7, 2026 05:59
@roncodes roncodes mentioned this pull request Oct 7, 2026
@roncodes
roncodes merged commit 6593455 into release/v0.6.72 Oct 7, 2026
8 of 9 checks passed
@roncodes
roncodes deleted the feature/structured-tracking-codes branch October 7, 2026 06:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant