XTensiv — Epicor ↔ Extensiv Integration: What It Does

A plain-English description of every automated event in the integration and what happens as a result.


The Big Picture

Epicor is the ERP — it owns customer orders, purchase orders, and financials. Extensiv (formerly 3PL Central) is the third-party warehouse — it owns physical inventory, picking, packing, and shipping.

The middleware (XTensiv) sits in between and translates. Epicor BPMs call the middleware; the middleware talks to both systems so neither has to know how the other works.

[Diagram]

Flow 1 — Outbound Shipment: Epicor Pack Slip → Extensiv Order

Trigger: An Epicor BPM fires when a pack slip is ready to be sent to the warehouse.

What the BPM sends to the middleware:

What the middleware does, step by step:

  1. Reads the pack slip from Epicor — pulls ShipHead (header) and ShipDtl (line items) via CustShipSvc.

  2. Looks up ship-to address — calls the ShipmentShipToAddress BAQ to get the actual delivery address (the ShipHead address fields can sometimes hold the bill-to address instead).

  3. Resolves Extensiv SKU codes — calls the ExtensivSKUDtl BAQ. For each pack line it finds the 3PL's SKU from PartWhse.sgc3rdPartyPart_c. If that field is blank, it falls back to the Epicor part number.

  4. Converts quantities to the right unit of measure — calls the PartUOMConv BAQ to get UoM conversions, then looks up each SKU's packaging info in Extensiv (e.g., "24 Each per Carton"). Converts the Epicor qty to whatever unit Extensiv wants for that SKU:

    • Packaging Unit (e.g., Carton) if the SKU has a packaging tier configured
    • Primary Unit (e.g., Each) if not
  5. Creates the order in ExtensivPOST /orders with the resolved SKUs, quantities, units, ship-to address, and carrier info. Uses the PackNum as the referenceNum so both systems can cross-reference.

  6. Handles freight billing — if the pack slip's billing code is FreightCollect or similar, includes a PayAccount block so Extensiv generates a third-party billing label.

  7. Writes the Extensiv order ID back to Epicor — patches ShipHead.sgc3PLRef_c so Epicor knows which Extensiv order corresponds to this shipment.

  8. Controls inventory allocation — by default, overrides Extensiv's automatic FIFO allocation with a smarter pallet/lot-aware one:

    • Reads the stock detail records for each SKU from Extensiv
    • Picks whole pallets first, then whole lots, then breaks only the smallest pallet that still covers the remaining quantity (FIFO tiebreaker)
    • Replaces Extensiv's auto-allocated picks with the chosen ones
    • If anything goes wrong in this step, leaves the order fully unallocated rather than letting a bad split through

What Epicor ends up with: ShipHead.sgc3PLRef_c filled with the Extensiv order ID.

What Extensiv ends up with: A new outbound order, allocated and ready for the warehouse team to pick.

[Diagram]

Flow 2 — Inbound Purchase Order: Epicor PO → Extensiv Receiver

Trigger: An Epicor BPM fires when a PO has been released and the goods are expected at a TPL-integrated warehouse.

What the BPM sends to the middleware:

What the middleware does, step by step:

  1. Reads PO release lines from Epicor — calls the Xtensiv_PO BAQ, which returns one row per release line that is destined for a warehouse flagged as TPLIntegratedWhse_c = 1. Lines going to non-TPL warehouses are excluded automatically by the BAQ.

  2. Resolves SKU codes — the BAQ's calculated Calculated_XTensivPartNum field already does this: it returns the 3PL SKU if the cross-reference is populated, otherwise falls back to the Epicor part number.

  3. Pulls vendor info — the BAQ also returns vendor name and address, which Extensiv needs to identify the supplier on the inbound receipt.

  4. Creates the receiver in ExtensivPOST /inventory/receivers with one line per PO release, using the PONum as the referenceNum.

  5. Writes the receiver ID back to Epicor — because approved PO headers can't be updated via a standard PATCH (they're locked), the middleware calls the Xtensiv.UpdatePOXRef Epicor Function, which writes the receiver ID into POHead.sgc3PLRef_c.

What Epicor ends up with: POHead.sgc3PLRef_c filled with the Extensiv receiver ID.

What Extensiv ends up with: A new inbound receipt record ready for the warehouse team to check in against.

[Diagram]

Flow 3 — Sync Shipment Status Back to Epicor

Trigger: An Epicor BPM polls the middleware periodically (or on demand) to see if the warehouse has finished processing a shipment.

What the BPM sends to the middleware:

What the middleware does, step by step:

  1. Looks up the Extensiv order ID — reads ShipHead.sgc3PLRef_c from Epicor to get the orderId.

  2. Fetches the current Extensiv orderGET /orders/{id}?detail=All&itemDetail=All to get status, line quantities, charges, and tracking.

  3. Checks if the order is closed — the reliable signal is readOnly.isClosed === true (not just the status code, which can lag). If it's not closed yet, stops here and returns the current status so the BPM can try again later.

  4. If the order is closed, it:

    a. Updates shipped quantities — for each line in the Extensiv order, converts the warehouse's actual originalPrimaryQty back to the Epicor UoM and updates ShipDtl.OurInventoryShipQty via the CustShipSvc BO method chain (direct PATCH won't work because it's a calculated field).

    b. Writes 3PL charges to Epicor as ShipMisc lines — pulls the billing charges from the Extensiv order (handling, storage, etc.) and creates misc charge lines on the Epicor shipment with the MiscCode TPL. The charges are marked up by the customer's sgc3PLHandlingMarkup_c percentage before being added.

    c. Updates the tracking number — patches ShipHead.TrackingNumber with whatever Extensiv has.

    d. Marks the shipment Ready to Invoice — sets ShipHead.ReadyToInvoice = true.

    e. Creates an AP Invoice for the 3PL vendor — creates an AP Invoice group, invoice header, and a job-miscellaneous detail line for the raw (pre-markup) charge amount. Allocates the cost across any project IDs from the TPLHandlingMarkup BAQ. The vendor ID comes from Warehse.TPLSupplierID_c. Invoice number follows the pattern TPL-{PackNum}-{extensivOrderId}.

What Epicor ends up with:

What Extensiv ends up with: Nothing changes — this is a read-only operation against Extensiv.

[Diagram]

Summary: When X Happens, Y Happens

When… The middleware… Epicor gets… Extensiv gets…
A pack slip is ready to ship to a 3PL warehouse Reads the pack slip, resolves 3PL SKUs and UoMs, creates an order in Extensiv with pallet-aware allocation ShipHead.sgc3PLRef_c = Extensiv order ID A new outbound order, allocated to specific pallets/lots
A PO is released to a 3PL warehouse Reads the PO lines (TPL warehouses only), resolves 3PL SKUs, creates a receiver in Extensiv POHead.sgc3PLRef_c = Extensiv receiver ID A new inbound receipt ready for check-in
A BPM polls to sync a shipment that the warehouse has closed Reads the Extensiv order, converts quantities back to Epicor UoMs, writes charges, updates tracking Corrected shipped qtys + 3PL charge lines + tracking number + ReadyToInvoice flag + AP Invoice for the 3PL vendor Nothing — read-only

Key Cross-Reference Fields

Field Table What it stores
sgc3PLRef_c (a.k.a. TPLPackNum_c) ShipHead Extensiv order ID for this outbound shipment
sgc3PLRef_c (a.k.a. TPLRecieptNum_c) POHead Extensiv receiver ID for this inbound PO
sgc3rdPartyPart_c (a.k.a. TPLPartXRef_c) PartWhse The warehouse's SKU code for this part
TPLIntegratedWhse_c Warehse Flag: this warehouse is managed by a 3PL
TPLSupplierID_c Warehse Epicor vendor ID for the 3PL (used on the AP Invoice)
sgc3PLHandlingMarkup_c Customer Markup % added on top of 3PL charges when billing the customer