Skip to main content

Ship Station page (label download / print)

The AdminJS "Ship Station" page where warehouse workers scan a work order, then download + print the shipment's shipping label.

Deep-dive doc split out of .claude/rules/architecture.md (which is now an index). Append new findings about this area here, not to the index.

File organization​

  • Virtual resource: src/routers/admin/resources/shipStation/shipStation.ts — ShipStation virtual resource; list action renders Components.ShipStation. Hidden resource actions: searchUnique, downloadShippingLabel, markLabelAsPrinted, markLabelAsShipped.
  • Handlers (same dir): searchUniqueHandler.ts (work orders for the scanned number, include: { customerShipment: true, ... }), downloadShippingLabelHandler.ts, MarkLabelAsPrinted.ts, MarkAsShipped.ts.
  • Frontend: src/components/admin/ShipStation/ — ShipStation.tsx (page), utils.ts (all handlers + hooks), ShipStationRTSModal.tsx (imperative ref.open() modal, hand-rolled Tailwind overlay), ShipStationCanceledSKUModal.tsx (controlled isOpen/onClose props, @adminjs/design-system Modal). Tests: utils.spec.ts (pure-function tests only, e.g. judgeRecordsIsReadyToPrintLabel).
  • Locale strings: components.ShipStation.* for modal copy, buttons.* for button labels (downloadShipmentLabel = "Download Shipping Label", confirm, cancel, yes, no exist).
  1. Worker scans → searchFetch(workOrderNumber) → searchUnique action → records (work orders + customerShipment). The scanned value is persisted in the URL as ?recName= and re-searched on mount.
  2. The button is enabled only when judgeRecordsIsReadyToPrintLabel(records) — every WO is Done and customerShipment.labelStatus is Acknowledged or Printed. So reprinting a Printed shipment is already allowed today, with no warning (INFRA-677 adds one).
  3. Click → handleDownloadLabel → downloadShippingLabel action. That handler short-circuits RTS shipments (isRTS, no PDF), otherwise calls the internal /shippinglabel/v1/getShippingLabel API, fetches the label URL, returns pdfBase64. A canceledWarehouse marker means no PDF (KY2) and the page re-fetches to open the canceled-SKU modal.
  4. changeUrlIsRTS → blob URL into a hidden <iframe> → triggerUpdateStatusAndPrint → handleMarkAsPrintedLabel (skips if already printed) → iframe.contentWindow.print(). There is no separate "print page" — printing is the browser print dialog over the hidden iframe.
  5. finally → refreshWorkOrders() re-runs the search, so labelStatus in records reflects Printed after the first print.

"Already printed" signal​

Use customerShipment.labelStatus === 'Printed' (compare case-insensitively, as handleMarkAsPrintedLabel does), not labelPrintedAt. labelPrintedAt is set only on the first transition (COALESCE in ShipmentModel.markPrintedManyAndReturn) and has been deliberately left NULL for some bulk-fixed Printed rows (INFRA-626), so it's not a reliable "has been printed" flag.

labelStatus in records is only as fresh as the last searchUnique call. That is sufficient because only one ship station prints shipping labels (product assumption, INFRA-677): every print on this page is followed by refreshWorkOrders(), so there is no other writer to go stale against. Revisit if a second label-printing station is added.

RTS shipments (customerShipment.isRTS, loaded on records via searchUnique's include) never produce a PDF — downloadShippingLabel short-circuits and the page just marks Printed + opens the RTS modal. Print-related guards (e.g. INFRA-677's reprint confirmation) exclude RTS.

Testing the page locally (INFRA-677)​

  • Stale components bundle. If dist/adminjs-bundles/ exists (e.g. from running scripts/build/bundle-components.ts), the dev server serves it instead of your working tree — new components silently don't appear. npm run dev deletes it first; if you start tsx src/index.ts directly, rm -rf dist/adminjs-bundles yourself. Confirm with curl -s localhost:3000/admin/frontend/assets/components.bundle.js | grep -c <newSymbol>.
  • Factory scoping. Search results are filtered by the sidebar's Current Factory; "No records" for a known work order usually means the wrong factory is selected.
  • No external side effects. Label download goes through the internal shipping-label API to Fulfil; mock downloadShippingLabel in the browser (Playwright context.route) with a tiny base64 PDF. markLabelAsPrinted is safe to let through locally with the test-mode feature flag on (it writes Printed to the local DB and skips fulfilShipmentDone); the flag is read at startup, so set it before launching the server.
  • Headless print eats input. After the hidden iframe calls print(), headless Chromium stops delivering real mouse clicks to the page (a document-level click listener records nothing). Reload the page between print scenarios, or use a DOM el.click() — this is a harness artifact, not an app bug.
  • zh-CN can't be viewed as the dev no-login user (availableLanguages: ['en']); log in as a user whose language includes zh-CN, or check window.REDUX_STATE.locale.translations['zh-CN'] for the strings.