A plain-English description of every automated event in the integration and what happens as a result.
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.
Trigger: An Epicor BPM fires when a pack slip is ready to be sent to the warehouse.
What the BPM sends to the middleware:
PackNum)What the middleware does, step by step:
Reads the pack slip from Epicor — pulls ShipHead (header) and ShipDtl (line items) via CustShipSvc.
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).
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.
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:
Creates the order in Extensiv — POST /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.
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.
Writes the Extensiv order ID back to Epicor — patches ShipHead.sgc3PLRef_c so Epicor knows which Extensiv order corresponds to this shipment.
Controls inventory allocation — by default, overrides Extensiv's automatic FIFO allocation with a smarter pallet/lot-aware one:
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.
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:
PONum)What the middleware does, step by step:
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.
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.
Pulls vendor info — the BAQ also returns vendor name and address, which Extensiv needs to identify the supplier on the inbound receipt.
Creates the receiver in Extensiv — POST /inventory/receivers with one line per PO release, using the PONum as the referenceNum.
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.
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:
PackNum, Company, PlantWhat the middleware does, step by step:
Looks up the Extensiv order ID — reads ShipHead.sgc3PLRef_c from Epicor to get the orderId.
Fetches the current Extensiv order — GET /orders/{id}?detail=All&itemDetail=All to get status, line quantities, charges, and tracking.
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.
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.
| 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 |
| 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 |