# Shipping & dispatch

How Business Central feeds a carrier or route-planning system and takes its results back. Every flow here is a **round trip**. Business Central pushes the data, the external system does the physical work, and the result is written back afterwards. Nothing in this extension blocks the warehouse waiting for a carrier.

## What this extension is, and what it is not

The integration is an **asynchronous system of record**. It is deliberately *not* a pack station and *not* a route planner:

- **Carrier and label systems** (ShipStation, DPD, UPS, DHL, 3PLs) own packing, rating, label generation, printing and pickup booking.
- **Route planners** (Stream, Descartes, Maxoptra) own route optimisation and the dispatch board.
- Business Central owns the documents, the stock movement and the accounting, and stores what the external system sends back.

```
BC data ──push──▶ external system (does the work) ──writeback (async, after)──▶ BC
```

> [!IMPORTANT]

> **When to use a dedicated shipping ISV instead.** If you need a *synchronous* cartonize-and-print experience at a high-volume pack station (the operator scans a box, and a label prints in the same breath), this is the wrong tool. Use a dedicated shipping ISV (Dynamic Ship, nShift, ShipStation's own Business Central connector) for that station, and use these endpoints for the data layer around it. This is a deliberate boundary, not a missing feature. The endpoints here are built for volume that arrives in batches, not for a keystroke-latency loop.

## Two entry paths, one writeback core

There is no single happy path. Which one you are on depends on whether Business Central runs the warehouse.

|  | **Simple**: no WMS | **WMS**: warehouse shipments |
| --- | --- | --- |
| Who fulfils | The external tool ships it | Business Central picks, packs and posts |
| Upstream anchor | [Sales Order](https://eonesolutions.github.io/eone-bc-integration/core-salesOrders.md) | [Warehouse Shipment](https://eonesolutions.github.io/eone-bc-integration/core-warehouseShipments.md) |
| Trigger | order **released** | **ready to ship** (packed) |
| Posting | external reports shipped → `markShipped` | Business Central posts the warehouse shipment |
| **Writeback lands on** | **[Posted Sales Shipment](https://eonesolutions.github.io/eone-bc-integration/core-salesShipments.md)** | **[Posted Sales Shipment](https://eonesolutions.github.io/eone-bc-integration/core-salesShipments.md)** |

Both paths converge on the **posted sales shipment**. That is where the tracking number and the label end up regardless of how the goods left the building, so a consumer reading shipment results only has to understand one shape.

```mermaid
flowchart LR
    A[Sales order released] -->|no WMS| B[Carrier or route planner does the physical work]
    A -->|WMS| C[Warehouse shipment packed]
    C --> B
    B -->|markShipped| M{Fulfilment Mode}
    M -->|Record Only or Release Only| G[Tracking stamped on the order, nothing posted]
    M -->|Post Shipment| D[Posted sales shipment]
    C -->|Business Central posts| D
    B -->|addFreightCharge| A
    D --> E[updateTracking writes the carrier number]
    D --> F[documentAttachments stores the label]
```

## Triggering the push

**Simple flow.** Subscribe a [realtime trigger](https://eonesolutions.github.io/eone-bc-integration/realtime.md) to `salesOrders` and filter for `status eq 'Released'`.

> **Caution:** **The subscriber owns fire-once.** Business Central change tracking fires on *every* modify, not on the Open→Released transition, and with no previous value to compare against. A released order that is later edited notifies again. Your integration must remember which orders it has already pushed and skip repeats; a repeat means a second label, a second parcel, and a second carrier charge for the same order. There is deliberately no "sent to shipping" flag in Business Central to lean on.

**WMS flow.** Subscribe to `warehouseShipments`. Weight and parcel count are not known until the shipment is packed, so the ready-to-ship state is the meaningful trigger, not order release.

## Recording the result

| What came back | Where it goes |
| --- | --- |
| One tracking number for the whole shipment | `updateTracking` on the posted shipment, or `markShipped` on the order |
| The carrier label file | [`documentAttachments`](https://eonesolutions.github.io/eone-bc-integration/core-documentAttachments.md) against the posted shipment |
| A freight charge to bill on | `addFreightCharge` on the order (inserts a real sales line) |
| Ordered drops for an own-fleet van | [`routeStops`](https://eonesolutions.github.io/eone-bc-integration/core-routeStops.md) |

> **Note:** **One tracking number per shipment in this release.** A shipment carries a single carrier tracking number, on the posted shipment header. Recording a shipment as several individually tracked boxes is not supported in the initial release. Where a carrier splits a shipment, write back the consignment-level tracking number and keep the per-box detail in the shipping system.

## Own-fleet route dispatch

For a company running its own vans, a **vehicle load is a warehouse document**, with no separate "run" or "trip" entity. The route planner pushes its plan into Business Central with one call per van:

- `createDeliveryLoad` on [salesOrders](https://eonesolutions.github.io/eone-bc-integration/core-salesOrders.md) assembles one [warehouse shipment](https://eonesolutions.github.io/eone-bc-integration/core-warehouseShipments.md) from several released orders (the bound order plus an `additionalOrderNumbers` list);
- `createPickupLoad` on [salesReturnOrders](https://eonesolutions.github.io/eone-bc-integration/core-salesReturnOrders.md) mirrors it inbound: one [warehouse receipt](https://eonesolutions.github.io/eone-bc-integration/core-warehouseReceipts.md) from several return orders.

The route id lands on the document's `integrationId`, the vehicle on its shipping agent + service, the route date on its shipment/receipt date, and the ordered drops (or collections) are written back as [routeStops](https://eonesolutions.github.io/eone-bc-integration/core-routeStops.md), which the warehouse sees on the native Business Central pages as the loading sequence. Delivery and collection progress flows back by PATCHing each stop's status (`Planned → Loaded → Delivered`, or `Collected` for a pickup).

## Weights, volumes and units

> **Note:** **Every weight and dimension in these endpoints is a bare number with no unit attached.** Business Central has no per-field unit of measure for mass or volume. The values are in whatever mass and length units the company is configured for. The integration map is responsible for translating them into what the carrier expects. Check the company's setup once, hard-code the mapping, and do not assume kilograms.

The same applies to the rollups (`totalWeight`, `totalVolume` on a warehouse shipment) and to the per-unit item values (`grossWeight`, `netWeight`, `unitVolume`). A line total is the per-unit value multiplied by the line quantity.

## Returns

The inbound mirror uses the same building blocks. A [Sales Return Order](https://eonesolutions.github.io/eone-bc-integration/core-salesReturnOrders.md) carries the return tracking and label, and Business Central's native RMA handles the receipt and credit memo. An own-van collection is a warehouse receipt with route stops, exactly mirroring a delivery.

## What is out of scope

Carrier API calls &middot; rate shopping &middot; label rendering and printing &middot; route optimisation &middot; a dispatch board &middot; a driver app or proof-of-delivery capture &middot; a synchronous pack station &middot; cartonization or per-box parcel records owned by Business Central &middot; delivery-status tracking after the parcel leaves. Those belong to the carrier or planner you are integrating with.
