Skip to content
Build 8b0b16e

NAV Order Release

What happens after NAV finishes allocating inventory for an order: NAV pushes XML to Camel, Camel fans the batch into one Service Bus message per order, each order is forwarded to OMS so the order can move out of PENDING_NAV_RELEASE.

This page covers the path inside Camel — from the inbound POST /nav/orders/release to the outbound PATCH on OMS. It does not cover the rest of the order lifecycle (OMS → SFCC, Cirro fulfillment, shipping confirmation) — see B2C Order Flow.

When this fires

  • B2C orders that NAV has finished allocating (e.g. printable orders waiting for assembly inventory). NAV emits one XML payload per ready batch; one batch can contain N orders.
  • The legacy B2B 940 path does not use this endpoint — that flow has its own NAV path (see EDI Pipeline).

High-level sequence

sequenceDiagram
    participant NAV
    participant Exp as cm-int-service-exp<br/>POST /nav/orders/release
    participant Blob as Azure Blob Storage<br/>nav-order-release container
    participant SB as Azure Service Bus<br/>queue: nav-order-release
    participant Prc as cm-order-prc<br/>SB consumer
    participant Osor as cm-osor-sys<br/>PUT /order/nav-release
    participant OMS

    NAV->>Exp: POST XML — Basic Auth — 1 batch, N orders
    Exp->>Exp: Split <Order> elements
    Exp->>Blob: Upload original full XML<br/>(audit copy)
    loop one message per order
        Exp->>SB: ServiceBusMessage<br/>body = single-order XML chunk<br/>props: NAVBufferId, blobUrl, X-B3-*
    end
    Exp-->>NAV: 200 — NAV order release queued for N orders

    loop each message
        SB->>Prc: deliver single-order XML
        Prc->>Prc: Parse Order → NavOrderDto<br/>(docNo, navBufferId, assemblies)
        Prc->>Osor: PUT /order/nav-release (JSON)<br/>via APIM (Subscription-Key)
        Osor->>Osor: @Valid (docNo, navBufferId,<br/>orderStatus, printableAttribute)
        Osor->>OMS: PATCH oms.navReleaseUpdate/docNo — X-USER-TOKEN
        OMS-->>Osor: 200
        Osor-->>Prc: 200
    end

Components

Component Role Key entry point
cm-int-service-exp Exposes the NAV-facing HTTPS endpoint, splits the batch, persists the raw XML, fans out to Service Bus. NAVController.javaNAVServiceImpl.processNavOrderRelease()
cm-order-prc Subscribes to the nav-order-release SB queue. Parses one order at a time, builds the JSON payload for OSOR. ServiceBusReceiverConfigurationNavOrderReleaseServiceImpl.processNavOrderRelease()direct:navOrderRelease route
cm-osor-sys Internal-only HTTP layer. Validates the JSON payload, calls the OMS NAV-release endpoint. OrderController#navReleaseOrderServiceImpl#navReleasedirect:navReleaseUpdate
Azure Blob Storage Stores the original full NAV XML for audit/replay. Blob name: <NAVBufferId>-<epochMs>.xml. container nav-order-release (per-env storage account)
Azure Service Bus Queue nav-order-release. maxDeliveryCount=1 — one consumer failure dead-letters immediately. queue per env (e.g. int-prod-nav-order-release)
OMS Updates the order state from PENDING_NAV_RELEASERELEASED_FROM_NAV and stores the assembly mappings. ${oms.navReleaseUpdate}/{docNo} (PATCH)

Per-order fan-out (exp layer)

The exp layer is the part that changed in CI-411. Pre-CI-411 the entire XML batch was published as a single SB message and the consumer had to iterate; if order 1 failed, orders 2..N were lost. Now one SB message = one order.

flowchart TD
    A[POST /nav/orders/release<br/>raw XML body] --> B[Spring Security<br/>Basic Auth check]
    B -->|401 if bad| Z1[401 Unauthorized]
    B --> C[NavMetaExtractor.split]
    C -->|no Order elements| Z2[200 — No orders to process]
    C --> D[Upload raw XML to Blob<br/>blobUrl = container/blobName]
    D --> E{For each NavOrderSplit}
    E --> F[Build ServiceBusMessage<br/>body = single-order XML]
    F --> G[Set application properties:<br/>NAVBufferId, blobUrl,<br/>X-B3-TraceId, X-B3-SpanId, X-B3-Sampled]
    G --> H[navOrderReleaseSender.sendMessage]
    H --> E
    E -->|all queued| I[200 — NAV order release queued for N orders]

Why blob first, then fan-out:

  • The blob is the auditable record of exactly what NAV sent — preserved even if the SB messages later move into the DLQ.
  • The blobUrl is attached to every per-order SB message, so the consumer can correlate any single order back to the full batch if needed.

Message contract — Service Bus

Field Value Notes
Body Single <Order>...</Order> XML chunk Content-Type: application/xml
Application property NAVBufferId The order's <NAVBufferId> text Used as the correlation ID through the rest of the chain.
Application property blobUrl Full Azure blob URL of the original batch For audit / debugging. The consumer does not re-download the blob.
Application property X-B3-TraceId / X-B3-SpanId / X-B3-Sampled Current B3 trace context, set from Tracer.currentSpan() The consumer copies these onto the HTTP call to OSOR so the trace stays connected across the SB hop in Datadog.

Consumer (cm-order-prc)

flowchart TD
    A[SB message received<br/>processNavOrderRelease] --> B{body empty?}
    B -->|yes| Z1[warn + skip]
    B --> C[parseSingleOrder<br/>XXE-hardened DocumentBuilder]
    C -->|no Order found| Z2[IllegalArgumentException → DLQ]
    C --> D[mapOrder:<br/>docNo, navBufferId,<br/>iterate Line → AsmToOrder → Assembly]
    D --> E[NavOrderDto<br/>orderStatus='nav_released']
    E --> F[direct:navOrderRelease<br/>set PUT + JSON + Subscription-Key]
    F --> G[osor-sys.nav.order-release URL<br/>via APIM]
    G -->|2xx| H[ack message]
    G -->|non-2xx| Z3[exception → DLQ<br/>maxDeliveryCount=1]

Assembly extraction: NAV inlines the assemblies under each <Line>, scoped by <AsmToOrder>. The consumer walks Order → Line → AsmToOrder → Assembly rather than a global getElementsByTagName("Assembly") so it cannot pick up an <Assembly> from a sibling order or an unrelated subtree.

OSOR-layer contract

PUT /order/nav-release on cm-osor-sys. JSON body:

{
  "docNo": "OW583018",
  "navBufferId": "PSA2434392",
  "orderStatus": "nav_released",
  "assemblyOrders": [
    {
      "orderLineNumber": "10000",
      "quantity": 1,
      "lotNumber": null,
      "requestedCompletionDate": "2026-05-20",
      "printableAttribute": "1"
    }
  ]
}

Validation (@Valid on the controller — fails with HTTP 400 before any work):

Field Constraint
docNo @NotEmpty
navBufferId @NotEmpty
orderStatus @NotEmpty
assemblyOrders[] @Valid (each element validated)
assemblyOrders[].orderLineNumber @NotEmpty
assemblyOrders[].printableAttribute @NotEmpty
assemblyOrders[].quantity @NotNull

OMS dispatch: PATCH ${oms.navReleaseUpdate}/{docNo} with X-USER-TOKEN (Base64-decoded from config at boot). The body is the JSON above, forwarded as-is. Any non-2xx from OMS surfaces as HTTP 500 to the caller (cm-order-prc).

Failure modes

Where Symptom Disposition
Basic auth fails at exp NAV gets 401 NAV retries per its own policy. Nothing reaches the queue.
Inbound XML has no <Order> elements exp returns 200 "No orders to process" and logs a warning No SB messages produced.
Blob upload fails exp returns 500 (NavReleaseProcessingException) No SB messages produced — NAV must resend the batch.
Single-order parse fails in consumer Exception → message DLQs (maxDeliveryCount=1) Other orders in the batch are unaffected.
OSOR validation fails (400) Consumer surfaces an exception → DLQ Bad payload; needs human review of the DLQ message.
OMS PATCH fails OSOR returns 500 → consumer surfaces an exception → DLQ Same.

The DLQ is the recovery surface for any per-order failure. Because each SB message is one order, replaying a single DLQ'd order does not re-deliver its siblings.

Configuration keys (Config Server)

All keys live in the per-service application file in PG (integration_int_service_sys_<env>.properties rows).

cm-int-service-exp

Key Purpose
nav.security.username / nav.security.password Basic auth that NAV uses. Stored literally — NoOpPasswordEncoder does a literal compare.
azure.servicebus.connection-nav-order-release SB namespace conn string.
azure.servicebus.queue-nav-order-release Queue name (e.g. int-prod-nav-order-release).
azure.storage.blob.connection-string Storage account conn string.
azure.storage.blob.container-name Container that receives the audit copies.

cm-order-prc

Key Purpose
azure.servicebus.nav-order-release.connection Consumer connection.
azure.servicebus.nav-order-release.queue Queue name (must match the exp side).
osor-sys.nav.order-release OSOR URL (APIM-fronted in nonprod/prod).

cm-osor-sys

Key Purpose
oms.navReleaseUpdate Base URL of the OMS NAV-release PATCH endpoint. The order's docNo is appended as /{docNo} at request time.

Tracing

The B3 trace context is carried across both async hops:

  • Exp → SB: Tracer.currentSpan() is copied into X-B3-TraceId, X-B3-SpanId, X-B3-Sampled SB application properties.
  • SB → Prc: those properties become Camel exchange headers and are re-attached to the outbound HTTP call to OSOR. A new span (navOrderRelease) is started on the consumer side so the wait time on the queue does not roll into the producer's span.

In Datadog, a multi-order batch shows up as one span on the exp side + N spans on the consumer side — all sharing the same TraceId (the NAV-side trace id, if NAV emits one, or the one Tracer creates at the controller).

See also