Skip to main content

SOP: Carrier Label Creation

Document ID: WMS-SHIP-001 Version: 1.0 Effective date: 04/30/2026 Owner: Warehouse Operations Manager Next review: [six months from effective date] Applies to: Pack-station staff completing the outbound flow; managers troubleshooting label failures and voiding bad labels


1. Purpose

This procedure governs how a PACKED order becomes physically shipable — generating one or more carrier labels via ShipEngine, capturing tracking numbers, decrementing inventory, transitioning the order to SHIPPED (or PARTIALLY_SHIPPED), and triggering downstream Shopify fulfillment sync. This is the last step in the WMS outbound flow before the order leaves the warehouse with the carrier.

Label creation is the only ShipEngine integration in the WMS. It's also the most expensive step to get wrong — once a label prints, the cost of correction (void, reissue, customer notification, refund) is materially higher than catching the mistake before the call. The pre-flight checks in §4.2 exist for exactly this reason.

2. Scope

In scope:

  • The pre-flight checks: order status, packing images, shipping address completeness
  • Sync path: POST /shipping/create-label — synchronous label creation, returns labels in the response
  • Async path: POST /shipping/create-label-async + SSE stream GET /shipping/labels/progress/:jobId — queued job with per-package progress events
  • USPS multi-package handling — special-case parallel-call behavior because USPS doesn't support multi-piece shipments at the ShipEngine API level
  • UPS/FedEx multi-package — single ShipEngine call returning N labels
  • Label voiding via POST /shipping/void-label (ShipEngine PUT /labels/:id/void)
  • Tracking via GET /shipping/track/:trackingNumber
  • Downstream Shopify fulfillment sync via the queued SYNC_SHOPIFY_FULFILLMENT job
  • W-Pulls tracking callback for orders sourced from W-Pulls

Out of scope:

  • Picking, packing, weighing, dimensioning — see WMS-PICK-001 through WMS-PACK-001
  • Carrier setup / API key management / preset configuration — out of routine ops; environment-level concern
  • Address validation as a standalone tool/shipping/validate-address exists but is consumed by the order detail / shipping form, not a routine procedure
  • Rate shopping for customer-facing checkout — this SOP covers the warehouse side; customer-facing rate quotes are an integration concern with the storefront
  • Customer-return labels — see WMS-RET-002 (different code path: ShipEngine return-label endpoint)

3. Roles & permissions

API enforcement:

  • GET /shipping/carriers — auth not enforced (designed as a public read for the order form)
  • POST /shipping/validate-address, POST /shipping/rates/:orderId, POST /shipping/create-label, POST /shipping/create-label-async, POST /shipping/void-label, GET /shipping/track/:trackingNumber, GET /shipping/order/:orderId/packages, GET /shipping/labels/job/:jobId, SSE GET /shipping/labels/progress/:jobId — all check request.user?.sub and return HTTP 401 Unauthorized if absent. No role gate.
RoleView carriers / trackValidate addressGet ratesCreate labelVoid label
READONLY
STAFF
MANAGER
ADMIN
SUPER_ADMIN

Operational expectations:

  • Voiding a label is a real money decision (carrier-specific void windows: USPS 30 days, UPS 90 days, FedEx 14 days, but the WMS doesn't enforce these — the ShipEngine API does). Manager review for non-trivial voids is the norm.
  • The async path is strongly preferred for orders with 3+ packages or any USPS multi-package shipment. The sync path can hang the page for 10+ seconds and timeouts on slow ShipEngine responses are unrecoverable.
  • Pickers / packers don't typically create labels — that's a pack-station role. The label creation usually fires from the fulfillment detail page after Order.status: PACKED.

4. Procedures

4.1 Pre-flight: prerequisites for label creation

Per ShippingService.createLabels (lines 528–600), the following checks fire before any ShipEngine call:

CheckFailureResolution
Order foundHTTP 404 Order {id} not foundVerify order ID. Has it been deleted?
Order status in ['PACKED', 'SHIPPED', 'PARTIALLY_SHIPPED']HTTP 400 Order must be packed before shippingPack the order first per WMS-PACK-001. The check accepts SHIPPED/PARTIALLY_SHIPPED so additional packages can be added to a partially-shipped order.
At least one packing image attached (order.packingImages.length > 0)HTTP 400 Order {orderNumber} cannot be shipped without packing imagesCapture a packing photo per WMS-PACK-001 §4.7. This is a hard gate — no images, no label.
Carrier code + service code valid (validateCarrierService)HTTP 400 with carrier-specific messagePick a valid carrier+service combo. The /shipping/carriers endpoint returns the valid combinations.
Shipping address present and complete (line1, city, state, zip, country)HTTP 400 Shipping address is required or address-field errorProvide a complete address on the order or via the shippingAddress body field.
Each package has positive weightHTTP 400 Package {n} must have a valid weightWeight comes from the pack flow (per WMS-PACK-001). If missing, re-pack with weight captured.

⚠ Packing-image hard gate is a real ergonomic constraint. Per WMS-PACK-001 §4.7, photos are optional and unenforced during packing — but required at shipping. This means an order can pack successfully and then fail at label creation with a confusing error. The fix is twofold: (a) train pack-station staff to always capture at least one photo (it's the easiest insurance against chargebacks); (b) consider making the photo requirement enforced at pack time per WMS-PACK-001 §8 so the failure happens earlier.

4.2 The two creation paths — sync vs. async

The WMS exposes two endpoints for label creation. Choose based on order size and reliability needs.

Sync (POST /shipping/create-label)Async (POST /shipping/create-label-async)
ResponseThe labels themselves, in the response bodyA jobId to poll/stream for progress
LatencyBlocks until ShipEngine finishes (10s+ for multi-package USPS)Returns immediately
Retry behaviorCaller retries the whole request on failureNo automatic retry (attempts: 1 is set explicitly per the route comment at lines 235–238 — ShipEngine label calls are not idempotent, so a retry could create a duplicate label and bill for it)
Progress eventsNonePer-package SSE stream via GET /shipping/labels/progress/:jobId
Best forSingle-package orders (UPS, FedEx, single-package USPS)Multi-package orders, USPS multi-package, any order where the user shouldn't wait
IdempotencyNone — caller must avoid double-submitJob ID is stable per order (create-label-${orderId} per line 231–234) — BullMQ deduplicates waiting/active jobs by ID, but allows new jobs after failure

The async path's attempts: 1 is deliberate. ShipEngine charges per label creation; a retry on a partial failure (label 1 created, label 2 failed) would create a duplicate label 1 and bill for it. The async path fails the entire job on any package-level failure, surfaces it to the user, and lets the user manually void any leaked labels and retry. See §7 troubleshooting for the recovery procedure.

4.3 Sync path — single-package label

Use when: Single-package order with UPS, FedEx, or a single-package USPS shipment. You're confident the call will complete in <10 seconds.

Steps:

  1. From the fulfillment page (per WMS-PACK-001), the ShippingLabelForm UI block calls POST /shipping/create-label with { orderId, carrierCode, serviceCode, packages: [...], shippingAddress?, items?, notes? }.
  2. The route validates pre-flight (per §4.1).
  3. The service runs createLabels which:
    • Normalizes the carrier code (frontend uses usps, ShipEngine expects stamps_com per resolveShipEngineCarrier at line 545).
    • Builds the warehouse and customer addresses.
    • Determines the branch (single vs. multi vs. USPS multi).
    • For single-package: builds one ShipEngine shipment with one package, POSTs to ${baseUrl}/labels, gets back a label.
    • Commits the labels via commitLabels — one big transaction per §4.5.
  4. Response contains: success: true, label, labels[0], totalCost, orderId, orderNumber, isTestLabel (true if SHIPENGINE_SANDBOX === "true").
  5. The UI shows the label, prints the PDF on the thermal label printer, and the order shows SHIPPED (or PARTIALLY_SHIPPED).

4.4 Async path — multi-package or USPS multi-package

Use when: Multi-package orders, any USPS shipment with >1 package, or any case where the user shouldn't wait on a sync response.

Steps:

  1. The UI calls POST /shipping/create-label-async with the same body shape as sync.
  2. The route enqueues a BullMQ job with stable ID create-label-{orderId} and attempts: 1. Returns { jobId } immediately.
  3. The UI opens an SSE connection to GET /shipping/labels/progress/:jobId?token={accessToken} (the SSE route requires token as a query parameter — line 447–448).
  4. As the worker (shipping.processor.ts:46-302) progresses, it emits LabelProgressEvent events:
    • For UPS/FedEx multi-package: one ShipEngine call returns N labels; the worker emits progress events as it processes each.
    • For USPS multi-package: each package gets its own ShipEngine POST /labels call; emits progress per package.
  5. On completion, the worker commits via commitLabels (same transaction as sync — see §4.5) and emits a final completion event with the labels and total cost.
  6. The UI shows progress, then the labels.
  7. Fallback: if the SSE connection drops, the UI can poll GET /shipping/labels/job/:jobId to read the current state of the job.

⚠ The SSE auth pattern is unusual. The SSE endpoint takes the access token as a query parameter (?token=...) rather than the standard Authorization header — because EventSource doesn't support custom headers. Per the route comment, verifyAccessToken(queryToken) is called explicitly. If you build a new SSE consumer, follow the same pattern.

4.5 What commitLabels writes

Both sync and async paths call commitLabels (per shipping.service.ts:1701–1924) at the end. This is one of the largest transactions in the WMS.

Per package label received from ShipEngine:

  • One ShippingPackage row with orderId, carrierCode, serviceCode, packageCode, trackingNumber, labelUrl, cost, currency, weight, dimensions (JSON), and one ShippingPackageItem per shipped SKU (sku, productName, quantity, unitPrice).

Per order, atomically:

  • All Allocation rows for the order with status: ALLOCATED are flipped to PICKED, pickedAt: now (catching any unbumped allocations).
  • For each shipped SKU: walks Allocation rows in allocatedAt ASC order (FIFO) and decrements the linked InventoryUnit.quantity by the shipped quantity. Throws Cannot ship {n} of {sku}: insufficient PICKED inventory if running short — this is the inventory-truth check.
  • Each OrderItem.quantityShipped increments by the shipped quantity (atomic increment, not overwrite).
  • All Allocation rows for the order with status: PICKED are flipped to RELEASED, releasedAt: now.
  • All OrderItem rows are re-read; if every item has quantityShipped >= quantity, Order.status: SHIPPED and shippedAt: now. Otherwise Order.status: PARTIALLY_SHIPPED.
  • The order's trackingNumber field gets all new tracking numbers joined with comma-separation (deduped against existing).
  • Best-effort: writes to fulfillment_events for downstream consumers.

Outside the transaction (after commit):

  • Shopify fulfillment sync is enqueued as a separate SYNC_SHOPIFY_FULFILLMENT job (per shipping.processor.ts:264–272). The job:
    • Skips orders without a shopifyOrderId.
    • Skips orders with sourceStore IN ('MANUAL', 'WPULLS', 'STOREFRONT') (these aren't Shopify orders).
    • Otherwise calls enqueueShopifyFulfillmentCreate with storeKey, trackingNumbers, trackingUrls, carrier, shippedItems, and notifyCustomer: true (default).
  • W-Pulls tracking callback runs if order.source === 'WPULLS' and wpullsShipmentRequestId is set. Calls notifyWPullsTracking(...) with the first tracking number.

Result:

  • The order is officially SHIPPED (or PARTIALLY_SHIPPED).
  • Inventory has been decremented from the source InventoryUnit rows.
  • One ShippingPackage per physical box exists with its label URL and tracking number.
  • Shopify (if applicable) gets the fulfillment marked as shipped within seconds.
  • The customer receives a Shopify-driven shipment notification email (default — controlled by notifyCustomer: true).

4.6 USPS multi-package — the special case

USPS does not support multi-piece shipments at the ShipEngine API level. If you need to ship 3 packages via USPS, ShipEngine requires 3 separate label calls — they cannot be combined into one shipment.

The service handles this transparently per shipping.service.ts:626–716:

needsSeparateLabels = isUsps(carrierCode) && packages.length > 1

When needsSeparateLabels is true:

  1. The service builds packages.length separate ShipEngine shipments, each with one package.
  2. All shipments are POSTed in parallel via Promise.allSettled.
  3. Successes are collected; failures are logged and excluded.
  4. If all fail, throws All label creations failed: {firstReason}.
  5. If any succeeded, those become the labels — partial success is allowed.

The packer sees this as N tracking numbers per order, even though "the customer received one shipment" — Shopify's partial-fulfillment quantity sync (per §4.5 callout) handles this correctly.

⚠ Partial USPS multi-package failure is the riskiest mode in this SOP. If 2 of 3 USPS labels succeed and 1 fails, you've got 2 paid-for labels with no recovery built in. The job won't auto-retry (per the attempts: 1 decision). Manual recovery: void the 2 successful labels per §4.7, fix whatever caused the failure (usually weight or address validation), retry. See §7 troubleshooting.

4.7 Voiding a label

Use when: Wrong carrier, wrong address, voided due to weight discrepancy, customer cancelled order before pickup, label was misprinted (rare — Zebra catches most), or recovery from a partial failure per §4.6.

Prerequisites:

  • The carrier-specific void window hasn't expired. WMS doesn't enforce this; ShipEngine returns an error if too late.
  • The label was created via WMS (you have labelId from the ShippingPackage.labelId or the original create response).

Steps:

  1. From the order detail page (or the shipping packages list), tap Void Label on the package.
  2. The UI calls POST /shipping/void-label with { labelId, packageId }.
  3. The service calls ShipEngine PUT /labels/{labelId}/void.
  4. On success, ShipEngine returns { approved: true, message }.
  5. The service updates ShippingPackage.voidedAt: now (per shipping.service.ts:1070–1075).
  6. Returns the void result to the UI.

What void does NOT do:

  • Does not reverse the inventory decrement. The commitLabels transaction already moved units from PICKED to consumed (decremented from InventoryUnit.quantity). Voiding the label does not restore the inventory. If you need to put the units back into inventory, that's a separate WMS-INV-001 §4.x action.
  • Does not change Order.status. A SHIPPED order with a voided label stays SHIPPED. If the order is being cancelled, that's a separate cancel-order workflow.
  • Does not refund the carrier charge automatically. ShipEngine processes the void on the carrier side; refund timing varies by carrier.

⚠ Voiding a label and reversing inventory are two separate operations. If the customer cancelled, you usually want both: void the label, then move the picked-but-not-yet-consumed-from-source units back to inventory via WMS-INV-001 §4.1. Don't assume void includes inventory rollback.

5. Reference

5.1 Sync vs. async — which to use

Order typePath
Single-package UPS/FedExSync — fast, simple
Single-package USPSSync — fast, simple
Multi-package UPS/FedExAsync preferred — UI shows progress
Multi-package USPS (any count)Async required in practice — multiple ShipEngine calls in parallel, sync would be 10s+
Any order from a slow connectionAsync — won't time out the request

5.2 ShipEngine carrier code mapping

The frontend uses friendly carrier codes that don't always match ShipEngine's expected codes. The service normalizes via resolveShipEngineCarrier at line 545:

Frontend codeShipEngine code
uspsstamps_com
upsups (no change)
fedexfedex (no change)
OtherPass-through (validate via validateCarrierService)

If you add a new carrier, both resolveShipEngineCarrier and validateCarrierService need updates plus the carrier presets in getCarriersAndPresets.

5.3 What the warehouse address looks like

Per shipping.service.ts:586–595, the warehouse address is built from environment variables:

FieldEnv varDefault
nameWAREHOUSE_NAMEWMS Warehouse
company_nameWAREHOUSE_COMPANYYour Company
address_line1WAREHOUSE_ADDRESS1123 Warehouse St
city_localityWAREHOUSE_CITYLos Angeles
state_provinceWAREHOUSE_STATECA
postal_codeWAREHOUSE_ZIP90210
country_codehardcodedUS
phoneWAREHOUSE_PHONE555-123-4567

If your environment variables are unset, labels print with placeholder addresses. Verify these are set in production. The defaults are dev-friendly but will create real labels with wrong shipper info.

5.4 Shipping events

Event typeWhen
order:shipped(via commitLabels events emit, captured in fulfillment_events)
order:partially_shippedWhen some items remain unfulfilled after commitLabels
Shopify fulfillment status updateVia the queued SYNC_SHOPIFY_FULFILLMENT job
  • WMS-PACK-001 §4.7 — Packing photos (the hard gate for shipping)
  • WMS-PICK-003 — Short-pick recovery (where PARTIALLY_SHIPPED orders originate)
  • WMS-INV-006 — Backorder resolution (for the unshipped portion of partial orders)
  • WMS-RET-002 — Customer return labels (different code path, similar ShipEngine integration)
  • WMS-AUD-002 — Shipping variance and chargeback investigation (when written)

6. Audit & compliance

The shipping flow has strong audit data — better than picking or packing:

  • ShippingPackage rows — one per physical box per order. Includes carrier, service, tracking number, label URL, cost, weight, dimensions, items list. Insert-only (no update except voidedAt).
  • ShippingPackageItem rows — one per shipped SKU per package. Sku, quantity, productName, unitPrice. Insert-only.
  • fulfillment_events rowsorder:shipped / order:partially_shipped per state transition.
  • Order.trackingNumber — comma-separated list of all tracking numbers ever issued for the order.
  • ShipEngine audit — ShipEngine itself is the carrier-facing audit log. You can correlate by labelId, trackingNumber, or shipment_cost against ShipEngine's dashboard.
  • Shopify fulfillment — the enqueueShopifyFulfillmentCreate call writes to Shopify's order fulfillment record, which is its own audit.
  • Inventory consumption — the Allocation.RELEASED status + InventoryUnit.quantity decrement happens in the same transaction; inventory_events may capture this depending on the inventory service's behavior.

Manager weekly review:

  • Pull ShippingPackage WHERE createdAt > now() - 7 days AND voidedAt IS NULL — all valid labels created this week. Sum cost for the carrier-cost report.
  • Pull ShippingPackage WHERE voidedAt IS NOT NULL AND createdAt > now() - 7 days — voids this week. High count = problem (wrong addresses, weight mismatches, picking errors).
  • Pull Order WHERE status = 'PARTIALLY_SHIPPED' AND updatedAt < now() - 24 hours — orders sitting partially shipped without follow-up. May need WMS-PICK-003 §4.4 (split) to track the unshipped portion as a child order.

Quarterly governance:

  • Carrier cost trend (sum of ShippingPackage.cost per carrier per month). Negotiate rates if trending high.
  • Void rate (voids / total labels). >2% indicates an upstream pack/address/weight problem.
  • USPS multi-package partial-failure rate. If non-zero, see §4.6 callout — investigate and fix.
  • Shopify fulfillment sync failure rate (queued vs. landed in Shopify). Check #wms-support queue health if drifting.

7. Troubleshooting

SymptomCauseResolution
Order must be packed before shipping (HTTP 400)Order is ALLOCATED, PICKED, or other non-pack statusPack the order per WMS-PACK-001.
Order {orderNumber} cannot be shipped without packing images (HTTP 400)Hard gate per §4.1Capture a packing photo per WMS-PACK-001 §4.7. Then retry.
Package {n} must have a valid weight (HTTP 400)Packing didn't capture weightRe-pack with weight. The pack-time form should require it; if the pack completed with weight 0, that's a UI bug.
Shipping address is required (HTTP 400)No address on the order or in the request bodyProvide via the order detail page or pass shippingAddress in the body.
Sync create-label hangs >30sShipEngine API slow, timeout imminentUse the async path. Sync should not be used for any order where 30s would matter.
Async job stuck in waiting for >5 minWorker queue is unhealthy or dead-letterCheck #wms-support queue health. If stuck, void any leaked labels and retry via async (the stable jobId will replace, not duplicate).
Async job failed with attempts: 1 and surfaced an errorPer §4.2, attempts: 1 is deliberate to prevent duplicate label chargesRead the error. Fix the underlying issue (weight, address, carrier service combo). Void any leaked labels per §4.7. Retry.
USPS 3-of-3 multi-package: 2 labels created, 1 failedThe Promise.allSettled succeeded the 2, surfaced the 1's errorDon't retry the whole call — that would create 2 more duplicate labels. Void the 2 successful labels per §4.7, fix the underlying issue, retry the whole order. The 2 voided labels will refund (carrier-dependent timeline).
Label voided but inventory wasn't returnedPer §4.7 callout, void doesn't reverse inventoryMove the appropriate units back to inventory via WMS-INV-001 §4.1.
Cannot ship {n} of {sku}: insufficient PICKED inventory (HTTP 500-ish)The commitLabels transaction calculated more shipped quantity than was actually allocated/pickedCheck Allocation rows for the order — there may be a mismatch between what's in the package items list and what's actually allocated. Cross-reference the pack flow per WMS-PACK-001 §4.6 callout (multi-bin overwrite bug).
Shopify shows the order as fulfilled even though I voided the labelShopify fulfillment was synced before void; voiding doesn't auto-roll back ShopifyManually update Shopify fulfillment status, or cancel-and-recreate via the Shopify admin. Fragile — flagged in §8.
isTestLabel: true in the responseSHIPENGINE_SANDBOX === "true" env varThis is dev/staging behavior. Verify production env doesn't have the sandbox flag set. Test labels are not real and won't ship.
Carrier code error unknown carrierFrontend sent an unmapped carrier codeAdd the mapping to resolveShipEngineCarrier per §5.2 or use one of the supported codes.
Tracking number missing from response despite successShipEngine returned a label but no tracking_numberRare — usually a carrier-side delay. Wait a few minutes and re-fetch via GET /shipping/order/:orderId/packages.
W-Pulls notification didn't fire after shipwpullsShipmentRequestId was null on the orderOrder wasn't from W-Pulls. Skip — this is correct behavior.

8. Escalation

  • Make the packing-image gate enforce at pack time. Per §4.1 callout, the photo requirement is enforced at shipping but optional at packing. This creates a confusing failure mode (pack succeeds, label fails). Move the gate up. Cross-reference WMS-PACK-001 §8.
  • USPS multi-package partial-failure recovery UI. Today the recovery from a 2-of-3 partial failure (per §4.6) is a manual void-then-retry dance. A UI flow that detects partial success, shows the user "2 created, 1 failed — void successes and retry, or accept partial?" would close the highest-friction shipping scenario.
  • Inventory rollback on void. Voiding a label doesn't reverse the inventory consumption. A voidAndRollback flow that voids the label and moves the SKU's quantityShipped decrement back into inventory atomically would close the §4.7 footgun.
  • Shopify fulfillment auto-rollback on void. Same problem at the Shopify integration layer. Today a voided label leaves Shopify thinking the order shipped. Add a fulfillmentVoid enqueue alongside enqueueShopifyFulfillmentCreate to keep both sides consistent.
  • Carrier rate caching. Live getRates calls for every order detail page view are slow and expensive. Cache rates per (orderId, address-hash) for 5–10 minutes. Engineering ticket.
  • Address validation as a hard gate. Today /shipping/validate-address exists as a separate call but isn't enforced. A bad address gets caught only at ShipEngine call time. Wire validation into the order form so bad addresses surface at order creation, not at label generation.
  • Suspected ShipEngine charges for voids that didn't refund. ShipEngine's accounting — outside the WMS, but worth correlating monthly. Pull ShippingPackage WHERE voidedAt IS NOT NULL from the past 60 days, cross-reference with ShipEngine's invoice for void credits. Discrepancies escalate to ShipEngine support.

9. Revision history

VersionDateAuthorChanges
1.0[DATE][NAME]Initial release. Documents both label creation paths (sync POST /shipping/create-label and async POST /shipping/create-label-async + SSE /shipping/labels/progress/:jobId) grounded in apps/api/src/routes/shipping.routes.ts:33-470, packages/domain/src/services/shipping.service.ts:528-1924, apps/worker/src/processors/shipping.processor.ts:46-449. Documents the packing-image hard gate (per shipping.service.ts:578-583) — the ergonomic disconnect with WMS-PACK-001 §4.7's optional photo. Documents the attempts: 1 design decision for async (per route comment lines 235-238) — deliberate non-retry to prevent ShipEngine duplicate-label charges. Documents the USPS multi-package special case (needsSeparateLabels = isUsps(carrierCode) && packages.length > 1 at line 628) and its Promise.allSettled parallel-call behavior with partial-success handling (lines 633-716). Documents the full commitLabels transaction (lines 1701-1924) — ShippingPackage creation, FIFO inventory decrement walk via Allocation rows, Order.status SHIPPED vs. PARTIALLY_SHIPPED determination based on per-item quantityShipped >= quantity check. Documents the post-transaction Shopify fulfillment enqueue (per shipping.processor.ts:264-273) and W-Pulls tracking callback (lines 276-294). Documents the void label flow including the explicit gap that void does NOT reverse inventory or roll back Shopify fulfillment. Documents the carrier code normalization (usps → stamps_com per resolveShipEngineCarrier). Cross-references WMS-PACK-001 (upstream gate), WMS-PICK-003 (PARTIALLY_SHIPPED origins), WMS-INV-006 (backorder for unshipped portion), WMS-RET-002 (return labels).