Cirro B2B label_template — Config-Driven Vendor Setup¶
Proposed design — not built yet
This is an agreed engineering design for Phase 1.5 of the Cirro B2B migration, not current system behavior — nothing here is wired. Ticket: CI-433 · Epic: CI-432 · Subtasks: CI-435–439 · Service: cm-edi-prc · Dated: 2026-06-02. The other half of Phase 1.5 — consuming Cirro's enriched shipping confirmation and completing the 856/945 — is CI-434, tracked separately.
Pairs with the per-vendor ground-truth pages
This is the send-side build design for the label payloads documented under Cirro B2B by Vendor. Those pages show what we send each retailer (real 850 + 940 + label_template); this page is how we'll build it.
The problem¶
Cirro is adding label requirements to the B2B Create flow. Each retailer wants a different set of shipping labels (carton/UCC, packing slip, pallet, GTIN), and each label wants a different set of fields. Walmart's carton label is not the same as Target's, which is not the same as Superior's.
Today our integration is vendor-blind — it builds one B2B Create payload the same way for everyone. That worked when nobody cared about labels. It won't work now, and we keep onboarding new retailers, so whatever we build has to make "add the next vendor" cheap and safe.
The thing we explicitly do not want is a hand-written code path per retailer. That doesn't scale, and every new vendor becomes a code change, a review, and a deploy.
The key insight¶
When we mapped the five vendors we have real orders for, one thing jumped out: where each field comes from never changes between vendors. The barcode always comes from the same place on the order. Our SKU always comes from the same place. The retailer's own item number, the department, the DC — all of them have a single, fixed source no matter which retailer we're shipping for.
What actually differs per vendor is only two things:
- Which labels that vendor wants (some want three, Amazon wants two), and
- Which fields go on each of those labels.
In other words, the per-vendor part is picking from a menu, not writing logic. Picking from a menu is exactly the kind of thing you put in configuration.
The approach¶
Two pieces:
- A shared library of field "readers" in code. One reader per field — barcode, our SKU, retailer SKU, department, DC, and so on. Each one knows the single place that field lives on the order and how to pull it. This is written once and almost never changes, because the sources are fixed.
- A per-vendor config file that just names which labels a vendor wants and which fields go on each. No logic, just lists.
At send time we look up the vendor by its trading-partner ID, read its config, and assemble the labels by calling the matching readers. The assembly loop is identical for every vendor — there is no per-vendor branching in the code at all.
Adding a new vendor is a config edit. You add a block naming its labels and fields. You don't touch code, unless the vendor needs a brand-new kind of field nobody has used before — and then it's one small reader added to the shared library, which is rare.
What adding a vendor looks like¶
This is the whole vendor definition — three real vendors shown. To onboard the next retailer, you add one more block like these:
cirro:
label-templates:
"005ALLPOPSOCKET": # Superior — 3 labels, no barcode
name: Superior
labels:
ucc_label:
items: [product_sku] # our SKU only
packing_slip_label:
items: [platform_sku] # Superior's item number
pallet_label:
items: [platform_sku, product_sku] # both
"009ALLPOPSOCKET": # Target — pallet carries barcode, slip has department
name: Target
labels:
ucc_label:
items: [platform_sku]
packing_slip_label:
header: [dept]
items: [product_sku]
pallet_label:
items: [platform_sku, upc]
"080ALLPOPSOCKET": # Amazon — 2 labels only
name: Amazon
labels:
ucc_label:
items: [upc]
pallet_label:
items: []
That readable list is the per-vendor work. Everything else is shared machinery. The three blocks above mirror the field lists on their ground-truth pages — Superior, Target, and Amazon.
Where the config lives¶
In the repo, alongside the code that reads it — not in the Config Server / database.
We talked this through. The Config Server's only real advantage is editing a vendor in production without a deploy. But that advantage evaporates exactly when it matters: the genuinely vendor-specific logic (e.g. Amazon's mixed-vs-single flag) always lives in code regardless, so any vendor that needs something new needs a deploy anyway. Meanwhile keeping the config in the repo means a typo in a field name fails the build instead of breaking a live order, every vendor change gets a code review, and we don't add a runtime dependency on the Config Server being healthy (which has bitten us before).
If vendor churn ever gets high enough that deploy-per-vendor genuinely hurts, the exact same config can move into the Config Server later without changing its shape. We're not betting against that future; we're just not paying for it now.
How it plugs into the existing flow¶
The B2B Create payload is assembled in one place in cm-edi-prc, and that place already has everything we need on hand: the original order document and the vendor's trading-partner ID. We add the label-building step there, attach the result to the outgoing payload, and we're done. The same step runs on both the direct path and the prepack path, since the labels belong on the B2B Create either way.
The label builder reads what it needs straight from the original order document. It does not reuse the existing line-item model, because that model throws away a couple of the fields the labels need (the barcode and the retailer's item number). Rather than reshape something that already works fine for its current job, the label step does its own focused read. Self-contained, nothing else disturbed.
Decisions made¶
- Config-driven via a field-reader library — no per-vendor code paths.
- Config lives in the repo, not the Config Server.
- The label step reads the order itself, decoupled from the existing line model.
- A vendor we haven't configured yet is skipped quietly (with a log line). The payload simply goes out without labels — exactly today's behavior — so an un-onboarded vendor degrades gracefully instead of failing the order.
- A configured field with no value is left out rather than sent blank.
To confirm with Cirro¶
These don't block the build; they're data questions to settle with Cirro.
- Amazon UPC — ours to source (resolved 2026-06-08). Amazon orders don't carry the UPC anywhere we can see, and Cirro has confirmed they cannot supply it (they hold multiple UPCs per item). So Amazon's UPC is ours to push — the plan is for NAV to populate
<ConsumerPackageCode>on the Amazon 940 (the Walmart pattern), or a product-master lookup. No longer a Cirro question; it's a sourcing/plumbing item for the builder. Until it's wired the field is left out (our leave-out-empty default). See the Amazon UPC note. - Per-vendor data gaps already noted in the vendor docs (e.g. Best Buy's GTIN-14) carry over here unchanged — same questions, now in the build context.
Out of scope¶
- Consuming Cirro's enriched shipping confirmation and completing the 856/945 — that's the other half of Phase 1.5, tracked separately (CI-434).
- Any change to how non-label parts of the B2B Create payload are built.
Related¶
- Cirro B2B by Vendor — Overview —
label_templatestructure, the three data gaps, and the open questions for Cirro. - Per-vendor ground truth: Amazon · Walmart · Best Buy · Target · Superior
- Cirro B2B API — Proposed Field Adjustments — the Cirro-side API shape this builds toward.
- EDI Pipeline — full 850→810 lifecycle context.
Design authored 2026-06-02 by cm-edi-prc (CI-433, Phase 1.5). Proposed — not yet implemented.