Skip to content

ADR-014: Shipment Return Flow

Status: Proposed Date: 2026-03-23

Context

When a warehouse fulfils an order, it pushes shipment data back to Portitor. This is the return direction of the integration: warehouse → platform → webshop. It is as important as the order pipeline (ADR-013) and carries equal complexity.

The shipment payload from the warehouse contains:

  • The warehouse's own transaction and document references (transactionNo, documentNo)
  • The warehouse location (locationCode)
  • Line-level quantities actually shipped — which may be less than what was ordered
  • Status and posting type

This payload must be correlated back to the original Portitor order, mapped to the webshop model, and used to update the order in the webshop: line item quantities, tracking numbers per line, and order status.

Partial fulfilment is a first-class case, not an edge case. A single order may receive multiple shipment events as one or more warehouse locations dispatch different line items across different times.

Decision

The shipment return flow follows the same configurable pipeline and event-handler model as the order pipeline (ADR-012). The warehouse adapter pushes a shipment event; fixed and configurable steps process it; fixed event handlers notify the source adapter in parallel.

Inbound API

The warehouse adapter plugin posts shipment data to:

POST /{company}/shipment

The platform:

  1. Writes the payload to S3 at the canonical key path (ADR-001 pointer pattern)
  2. Fires ShipmentReceived
  3. Returns 202 immediately — processing is asynchronous

Fixed Steps

Three steps are always present and cannot be removed:

  1. Ingest — writes the shipment payload to S3, fires ShipmentReceived, enqueues the pointer. Always first.
  2. Correlate — resolves the warehouse's documentNo to the Portitor order UUID and tenant using the warehouse-facing reference recorded in OrderSentToWarehouse (ADR-013). This reference was generated by the mapper at dispatch time specifically to make this lookup unambiguous — a single warehouse may serve multiple tenants, and the reference encodes enough to identify both the order and the correct tenant. Lookup is a direct S3 key path match (ADR-002), not a search. Always second. If the reference cannot be resolved, fires ShipmentCorrelationFailed and halts — no subsequent step can operate without knowing which order and tenant the shipment belongs to.
  3. Apply — calls the source adapter to update the order in the webshop: line item quantities, tracking numbers, and order status. Fires ShipmentApplied. Always last.

Fixed Event Handlers

  • ShipmentReceivedReporter — reacts to ShipmentReceived; records the raw receipt timestamp and warehouse reference in the dashboard order timeline. Runs in parallel with pipeline processing.
  • ShipmentAppliedNotifier — reacts to ShipmentApplied; triggers downstream notifications (e.g. Business Central sync, merchant alerts). Runs in parallel after the pipeline completes.

Configurable Steps

All steps between Correlate and Apply are optional and operator-configured:

StepDescription
mapMaps the warehouse shipment model to the webshop model — produces status, per-line item updates, tracking numbers, and reduced items
add-order-noteAdds a note to the order in the webshop confirming the shipment was received and correlated
customs-declarationPosts a customs declaration for the shipment (cross-border only — requires route context from the original order)

Partial Fulfilment

A shipment may fulfil only a subset of the ordered line items. The map step produces:

  • updates — per line item: quantity actually shipped and tracking number
  • reduced — items where shipped quantity is less than ordered quantity, with both wanted and actually values recorded

Partial fulfilment is a valid state, not a failure. Portitor's responsibility ends at updating the webshop order with the actual quantities and tracking via the source adapter. What the webshop does with that information — adjusting the charged amount, marking the order fulfilled, backordering remaining items — is the webshop's own concern and outside Portitor's scope.

The reduced[] field on ShipmentApplied carries the data downstream plugins need to act on partial fulfilment. For example, a Business Central integration plugin can subscribe to ShipmentApplied and update the BC order with the reduced quantities — this is a configurable event handler, not a platform concern.

Customs Declaration

When a customs-declaration step is configured, it runs after map and uses the mapped line items (HS codes, quantities, weights, receiver details) to post a declaration to the configured customs authority. This step is only meaningful for cross-border shipments.

Route context (origin country, destination country) is resolved from the original order's pipeline context stored at OrderSentToWarehouse time. If route context is absent, the step behaves identically to the ai-validate step in ADR-012: it skips the declaration and records an info finding.

Correlation Failure

ShipmentCorrelationFailed means the warehouse posted a documentNo that does not match any known Portitor order. The shipment payload remains in S3. The failure appears in the dashboard with the warehouse reference for manual operator resolution. The operator can:

  • Correct the reference and reprocess via POST /{company}/shipment/{id}/reprocess
  • Dismiss the shipment if it is genuinely unknown

Domain Events

EventFired when
ShipmentReceivedShipment payload accepted and written to S3
ShipmentCorrelationFailedWarehouse documentNo could not be resolved to a Portitor order
ShipmentAppliedShipment processed and applied to the order in the webshop

Event Shapes

ShipmentApplied carries the full outcome of the shipment:

json
{
  "shipmentId": "...",
  "orderId": "...",
  "warehouseRef": "...",
  "locationCode": "...",
  "status": "...",
  "updates": [
    {
      "lineItemId": "...",
      "quantityShipped": 2,
      "trackingNumber": "..."
    }
  ],
  "reduced": [
    {
      "name": "Blue Widget L",
      "wanted": 3,
      "actually": 2
    }
  ]
}

Projections

ProjectionContents
Unhandled ordersRemoves an order when ShipmentApplied is received and all line items are fulfilled
Partially fulfilled ordersOrders with at least one ShipmentApplied but remaining open line items
Failed correlationsShipments in ShipmentCorrelationFailed state, with warehouse reference, visible in dashboard for operator resolution

Both projections are rebuildable from the event log at any time (ADR-010).

Default Pipeline

ingest → correlate → map → apply

API Surface

EndpointMethodRoleDescription
/{company}/shipmentPOSTwarehouse-adapterIngest a shipment from the warehouse
/{company}/shipment/{id}/reprocessPOSTplatform-operatorReprocess a shipment after manual correlation fix

Consequences

Positive:

  • Partial fulfilment is modelled explicitly — reduced[] on ShipmentApplied gives downstream plugins (BC integrations, webshop handlers) the data they need without Portitor owning the financial or fulfilment logic
  • Correlation failure is recoverable — the payload is in S3, the operator resolves the reference and reprocesses without re-submission from the warehouse
  • Customs declaration is a configurable step — domestic shipments carry no declaration overhead
  • The same pipeline and event-handler model as ADR-012 — no new primitives

Negative:

  • Correlation depends on the warehouse returning the same documentNo that was in the original order. If the warehouse internally splits or renumbers an order, correlation fails and requires operator intervention
  • Multiple partial shipments for one order require the partially fulfilled projection to accumulate state across events — a projection that must handle repeated ShipmentApplied events for the same order correctly

Alternatives Considered

  • Synchronous processing (block until applied): Rejected — the warehouse adapter should not wait for the webshop update to complete; same reasoning as order ingest in ADR-013
  • Merge with ADR-013: Rejected — the return flow has a distinct trigger, distinct correlation step, distinct partial fulfilment semantics, and distinct failure modes; combining them obscures both directions

Released under the MIT License.