NAV Carton-Quantity Lookup¶
Proposed design — not built yet
This is an agreed engineering design, not current system behavior — nothing here is wired. Ticket: CI-494 · Services: cm-int-service-exp, cm-product-prc, cm-cirro-sys · Drafted: 2026-07-08.
Precondition (blocking)
NAV wants predict_pcs_inner ("Forecast Qty per Original Carton"). As of 2026-07-08 the Cirro Open API product/list returns predict_pcs_inner: null for every PopSockets SKU tested in prod, even though the Cirro portal UI shows it populated (open API-vs-UI discrepancy — separate write-up).
This design is not buildable end-to-end until Cirro makes product/list return predict_pcs_inner (matching what the UI shows). The Camel/NAV plumbing can be built and tested against the response shape in parallel, but real values depend on Cirro fixing the API.
Note: receive_pcs_inner — actual received qty per carton — is populated for ~95% of SKUs today. Not what NAV asked for, but it's proof that the pcs-per-carton concept works over the API and a possible fallback if predict_pcs_inner can't be fixed.
Goal¶
Let NAV fetch predict_pcs_inner (units per original carton) for a set of SKUs, to build packing instructions. Cirro is the source of truth; Camel exposes it to NAV.
Design principles: synchronous request/response (no Service Bus — this is a lookup, not an event), bulk (one NAV call → many SKUs), and machine-actionable (boolean exists + null-or-value, no free-text notes).
Architecture¶
NAV talks to the experience layer it already authenticates against (cm-int-service-exp, the same home as POST /nav/orders/release). The exp layer forwards to cm-product-prc, which calls the Cirro system layer (cm-cirro-sys), processes the raw Cirro payload, and maps the fields back to the NAV response shape. No new infrastructure.
sequenceDiagram
participant NAV
participant Exp as cm-int-service-exp<br/>POST /nav/product/carton-quantity
participant Prc as cm-product-prc<br/>POST /nav/product/list
participant Sys as cm-cirro-sys<br/>POST /nav/product/list
participant Cirro as Cirro Open API<br/>(oms.elogistic.com)
NAV->>Exp: POST JSON — APIM key + basic auth<br/>{ "skus": [...] }
Exp->>Exp: Validate & dedupe SKU list
Exp->>Prc: POST /nav/product/list<br/>{ "skus": [...] }
loop chunks of ≤200 SKUs
Prc->>Sys: POST /product/list (chunk)
Sys->>Cirro: POST /v1/auth/get-access-token (cached)
Sys->>Cirro: POST /v1/product/list<br/>{ product_sku_list: [...] }
Cirro-->>Sys: page of product records
Sys-->>Prc: raw Cirro product data
end
Prc->>Prc: Aggregate chunks + map fields<br/>(predict_pcs_inner, exists)
Prc-->>Exp: mapped results
Exp-->>NAV: 200 — { requested, returned, results[] }
| Component | Role |
|---|---|
| cm-int-service-exp | New NAV-facing REST endpoint. Validates/dedupes the SKU list, forwards to cm-product-prc, returns the shaped response to NAV. |
| cm-product-prc | Receives the SKU list from exp, chunks into ≤200-SKU batches, calls cm-cirro-sys per chunk, aggregates raw results, and maps Cirro fields to the NAV contract (predict_pcs_inner, exists). |
| cm-cirro-sys | New POST /product/list proxy route — extends the existing CirroProductAPIRouter (which already proxies unit-list / wrapper-code-list with the same product_sku_list shape). Handles token caching and pagination. |
NAV → Camel contract¶
Request¶
Response¶
{
"requested": 5,
"returned": 5,
"results": [
{ "sku": "807259", "predict_pcs_inner": 40, "exists": true },
{ "sku": "806876", "predict_pcs_inner": 40, "exists": true },
{ "sku": "807396", "predict_pcs_inner": 160, "exists": true },
{ "sku": "707203", "predict_pcs_inner": null, "exists": true },
{ "sku": "999999", "predict_pcs_inner": null, "exists": false }
]
}
predict_pcs_inner— the value straight from Cirro (same field name NAV/the team already reference).nullwhen the SKU has no value set or doesn't exist.exists— whether the SKU is in the Cirro catalog. NAV branches onexists+ null-check:
exists |
predict_pcs_inner |
Meaning | NAV action |
|---|---|---|---|
true |
non-null | SKU in Cirro, value set | use the value |
true |
null |
SKU in Cirro, value not set yet | apply fallback |
false |
null |
SKU not in Cirro catalog | apply fallback / flag bad SKU |
The response array preserves the caller's SKU set. A SKU-keyed map variant is trivial if NAV prefers it (open question — see below).
Field mapping (Cirro → Camel)¶
| Camel field | Cirro source | Rule |
|---|---|---|
predict_pcs_inner |
predict_pcs_inner |
pass through verbatim (null if absent) |
exists |
derived | true if the SKU appears in data.list[], else false |
Field mapping is performed by cm-product-prc after aggregating the raw Cirro chunks — direct 1:1, no coalescing, no field renaming.
Bulk & pagination¶
- Cirro
product/listcapspage_sizeat 200 and accepts aproduct_sku_listarray. - Camel chunks the incoming SKU list into batches of ≤200, issues one Cirro call per batch, and aggregates the results. One NAV request of any size = a handful of internal calls (e.g. 500 SKUs → 3 Cirro calls).
- Cirro rate limit is 600 req/min — well clear for NAV's expected usage.
Auth (each hop)¶
| Hop | Auth |
|---|---|
| NAV → cm-int-service-exp | APIM subscription key + basic auth (existing NAV pattern — same as /nav/orders/release) |
| cm-int-service-exp → cm-product-prc | Internal (APIM/direct, existing) |
| cm-product-prc → cm-cirro-sys | Internal (APIM/direct, existing) |
| cm-cirro-sys → Cirro | Authorization: Bearer <token> from /v1/auth/get-access-token, token cached until expiry (existing AuthService) |
Error & edge handling¶
- Per-SKU outcomes are carried by
exists+ the null-or-value ofpredict_pcs_inner— never dropped, never prose. - Duplicate SKUs in request → deduped before forwarding to Cirro; response reflects the unique set.
- Cirro unavailable / auth failure → request-level
5xxwith an error envelope (the only failure mode that isn't per-SKU). NAV retries or degrades. - Empty
skusarray →400 Bad Request.
Config & environments¶
Add cirro-sys.product.list URL to the config server per env — same pattern as the existing cirro-sys.product.unit-list and wrapper-code-list keys.
| Environment | Cirro base URL |
|---|---|
| prod | https://oms.elogistic.com/v1 |
| nonprod (dev/qa/stage) | https://uat-oms-elogweb.eminxing.com/v1 |
Out of scope¶
- NAV-side packing-instruction logic — the bulk of the effort, owned by the NAV team; this design covers only the Camel lookup contract.
- No Camel-layer caching initially (Cirro is fast + rate limit is generous); revisit if NAV polls heavily.
- No write-back to Cirro.
- Not tied to the Cirro 1.5 release — separate deployment, no Cirro-code dependency.
Open questions¶
- NAV fallback when
predict_pcs_inneris absent (exists: true+ null, orexists: false) — what does NAV do? - Array vs. SKU-keyed map for the response
results— confirm with NAV. - Optional single-SKU convenience route (
GET /nav/product/{sku}/carton-quantity) alongside the bulk POST?
See also¶
- NAV Order Release — the existing NAV-facing endpoint in
cm-int-service-exp; same auth pattern. - Camel Topology — where
cm-int-service-exp,cm-product-prc, andcm-cirro-syssit in the service graph.