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—ShipStationvirtual resource;listaction rendersComponents.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(imperativeref.open()modal, hand-rolled Tailwind overlay),ShipStationCanceledSKUModal.tsx(controlledisOpen/onCloseprops,@adminjs/design-systemModal). 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,noexist).
Print flow (data patterns)
- Worker scans →
searchFetch(workOrderNumber)→searchUniqueaction →records(work orders +customerShipment). The scanned value is persisted in the URL as?recName=and re-searched on mount. - The button is enabled only when
judgeRecordsIsReadyToPrintLabel(records)— every WO isDoneandcustomerShipment.labelStatusisAcknowledgedorPrinted. So reprinting aPrintedshipment is already allowed today, with no warning (INFRA-677 adds one). - Click →
handleDownloadLabel→downloadShippingLabelaction. That handler short-circuits RTS shipments (isRTS, no PDF), otherwise calls the internal/shippinglabel/v1/getShippingLabelAPI, fetches the label URL, returnspdfBase64. AcanceledWarehousemarker means no PDF (KY2) and the page re-fetches to open the canceled-SKU modal. changeUrlIsRTS→ blob URL into a hidden<iframe>→triggerUpdateStatusAndPrint→handleMarkAsPrintedLabel(skips if alreadyprinted) →iframe.contentWindow.print(). There is no separate "print page" — printing is the browser print dialog over the hidden iframe.finally→refreshWorkOrders()re-runs the search, solabelStatusinrecordsreflectsPrintedafter 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 runningscripts/build/bundle-components.ts), the dev server serves it instead of your working tree — new components silently don't appear.npm run devdeletes it first; if you starttsx src/index.tsdirectly,rm -rf dist/adminjs-bundlesyourself. Confirm withcurl -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
downloadShippingLabelin the browser (Playwrightcontext.route) with a tiny base64 PDF.markLabelAsPrintedis safe to let through locally with thetest-modefeature flag on (it writes Printed to the local DB and skipsfulfilShipmentDone); 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 DOMel.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 whoselanguageincludes zh-CN, or checkwindow.REDUX_STATE.locale.translations['zh-CN']for the strings.