SaveOrderswithItems - Logic Flow

SaveOrderswithItems - Logic Flow

SaveOrdersWithItems — Logic Flow

  Both entry points (POST /API/Orders/saveOrdersWithItems v1 and POST /api/v3/order/save-orders-with-items v3) share the same SaveOrdersInternal() core. The only difference is v3 returns structured ApiResult with proper HTTP status codes;   v1 always returns 200.

1 · Entry, Setup, and Per-Order Loop

Overall structure of SaveOrdersInternal(). Each order in ordersContainer.Orders is processed independently.

flowchart TD
  A([POST saveOrdersWithItems\nor save-orders-with-items]) --> B{APIKey valid?}
  B -->|No| C([Return 403 Forbidden])
  B -->|Yes| D[Lookup Account by APIKey]
  D --> E{ordersContainer null\nor Orders empty?}
  E -->|Yes| F([Return OK - nothing to import])
  E -->|No| G[importHistoryManager.Start\nrecord import session]
  G --> H[Load account settings\nzones, units of measure\ncapacity type config]
  H --> I{ConsolidateOrders == true?}
  I -->|Yes| J[consolidate = true\nshipment grouping enabled]
  I -->|No| K[consolidate = false]
  J --> L
  K --> L

 

  L([foreach OrderWithItemsModel in Orders]) --> M

 

  subgraph PERORDER [Per-Order Processing]

 

    M[Find existing order\n1 by Id\n2 by OrderNumber\n3 by CustomerReference\nunless IgnoreCustomerReferenceMatch]

 

    M --> N{Triton integration active\nand manifest passes check?}
    N -->|Yes| O[Save barcodes only\nSKIP all other processing\ncontinue to next order]
    N -->|No| P{Order already completed?\nDelivered or PickedUp\nor FutileCancelled}
    P -->|Yes| Q[SKIP - no changes allowed\ncontinue to next order]
    P -->|No| R[Resolve Sender and Receiver clients\nAddOrUpdateClient\nGeocode if address missing\nKeepExistingAddress skips geocoding]

 

    R --> S{Sender and Receiver\nmatch order type?}
    S -->|No| T[Log mismatch\nreturn 422 if v3\nthrow if v1]
    S -->|Yes| U{Existing order found?}

 

    U -->|Yes - update| V{StatusId == Cancelled?}
    V -->|Yes| W[Update StatusId to Cancelled\nAppend OrderDetails\nSAVE order]
    V -->|No| X[Check status transition validity\nFull field update\ncarrier, dates, addresses\ntolerance, service time\nnotification email and SMS]

 

    X --> Y{order.StatusId == Pending?}
    Y -->|Yes - rewrite entities| Z[Clear existing Barcodes\nUnits, OrderItems, Attributes\nSAVE and reload]
    Y -->|No - in-progress| AA[updateOrderEntities = false\nleave child entities intact]

 

    U -->|No - new| AB[Create new Order entity\nStatusId = Pending\nAccountId, TypeId, dates\naddresses, flags, price\ndb.Orders.Add]

 

    Z --> AC
    AA --> AC
    AB --> AC
    W --> AC

 

    AC[SAVE order\naffectedOrders++] --> AD{updateOrderEntities?}
    AD -->|Yes| AE[SaveOrderItems\nSaveUnits\nSaveAttributes\nSaveBarcodes]
    AD -->|No| AF
    AE --> AF[SAVE child entities]

 

  end

 

  AF --> AG{consolidate == true?}
  AG -->|Yes| AH[ProcessShipmentConsolidation\nsee Diagram 2]
  AG -->|No| AI[targetShipment = null]
  AH --> AJ[targetShipment returned]
  AI --> AK
  AJ --> AK

 

  AK{Manifest processing needed?\nSee conditions in note below} -->|Yes| AL[SaveManifest\nsee Diagram 3]
  AK -->|No| AM
  AL --> AM[next order]
  AM --> L

 

  L --> AN

 

  subgraph BREAKS [Breaks Processing - after order loop]
    AN{ordersContainer.Breaks\nnot empty?} -->|No| AO
    AN -->|Yes| AP[foreach BreakTimeModel]
    AP --> AQ{ManifestName and\nStartTime present?}
    AQ -->|No| AR[skip this break]
    AQ -->|Yes| AS[Find Manifest by\nname + date]
    AS --> AT{Manifest found?}
    AT -->|No| AR
    AT -->|Yes| AU{Find existing BreakTime\nby Id or by StartTime}
    AU --> AV{StatusId == Cancelled?}
    AV -->|Yes| AW[Remove ManifestItem\nRemove BreakTime\nSAVE]
    AV -->|No| AX{Existing break found?}
    AX -->|Yes| AY[Update BreakTime fields\nSAVE]
    AX -->|No| AZ[Create new BreakTime\nCalculate QueueNr\nby StartTime insertion\nAdd ManifestItem with BreakTimeId\nSAVE]
    AW --> AO
    AY --> AO
    AZ --> AO
    AR --> AO
  end

 

  AO[Final db.SaveChanges] --> AP2[UpdateManifests\nManifestManager recalculates\nall affected manifests]
  AP2 --> AQ2([Return OK\nImport complete])
Manifest processing conditions — any one of these triggers SaveManifest():
  • New order + no manifest name + zones configured → zonal manifest allocation
  • New order + (CreateManifest OR AllocateToManifest) + manifest name supplied
  • Existing order + (UpdateManifest == true OR UpdateManifestComplete == true)
  • Existing order already on a manifest + delivery or pickup date has changed

2 · Shipment Consolidation — ProcessShipmentConsolidation()

Only entered when ConsolidateOrders == true. Determines whether this order belongs in an existing shipment or forms a new one.

flowchart TD
  A([ProcessShipmentConsolidation\norder, thisAccount, existingShipment]) --> B{Order already\nin a shipment?}
  B -->|Yes| C[Load existingShipment\nwith all peer orders]
  C --> D{Order still matches\nshipment criteria?\nSame type, client,\naddress, and date}
  D -->|No| E[RemoveOrderFromShipment\norder.ShipmentId = null]
  E --> F{Remaining active orders\nin shipment <= 1?}
  F -->|1 order left| G[Dissolve shipment\nconvert ShipmentManifestItems\nback to OrderId\nfor remaining order]
  F -->|0 orders left| H[Remove all ManifestItems\nfor shipment]
  G --> I[existingShipment = null]
  H --> I
  D -->|Yes - still belongs| J[Keep existingShipment]
  J --> K
  I --> K

 

  B -->|No| K

 

  K[FindMatchingOrdersForShipment\nsame type + client + address + date\nexcluding cancelled] --> L{Any matching orders?}
  L -->|No| M([Return null\nno consolidation])

 

  L -->|Yes| N{Any matching order\nalready has a shipment?}
  N -->|Yes| O[Use that shipment\nas targetShipment]
  N -->|No| P[Create new Shipment\nAssign DeliveryKey\nand PickupKey\nSAVE]
  O --> Q
  P --> Q

 

  Q[Assign this order\nto targetShipment\norder.ShipmentId = targetShipment.Id] --> R[ConvertOrderManifestItemsToShipment\nfor each existing ManifestItem with OrderId\nif no ShipmentItem exists\nswitch OrderId to ShipmentId\nelse remove duplicate]

 

  R --> S[Assign all matching orders\nnot yet in this shipment\nConvertOrderManifestItemsToShipment\nfor each]

 

  S --> T([Return targetShipment])
Shipment criteria (all must match):
  • Same TypeId
  • Same delivery client + address + delivery date (Delivery / Service / PickupDelivery)
  • Same pickup client + address + pickup date (Pickup / PickupDelivery)

3 · Manifest Allocation — SaveManifest()

  Handles finding or creating the right manifest, updating driver/vehicle assignments,   clearing stale links, and writing new ManifestItem rows.   Behaviour differs significantly between UpdateManifest (basic) and UpdateManifestComplete (full).

flowchart TD
  A([SaveManifest called]) --> B{removeManifest?\nManifestName == double-asterisk}

 

  B -->|Yes| C[clearExistingManifestLinks = true\nmanifestName = empty]
  B -->|No| D[Resolve manifestName\nfrom orderWithItems.ManifestName\nor ordersContainer.ManifestNumber\nor existingManifest.name]

 

  C --> E
  D --> E

 

  subgraph DRIVER [Resolve Driver]
    E[Lookup driver by name\nfrom order or container] --> F{UpdateManifestComplete?}
    F -->|Yes - fullUpdates| G[Always update driver\nupdateManifestDriver = true]
    F -->|No - basicUpdates or new manifest| H[Set driver only if\nmanifest currently has none\nnewManifestDriver = true]
  end

 

  subgraph VEHICLE [Resolve Vehicle]
    I[Lookup vehicle by name\nfrom order or container] --> J{UpdateManifestComplete?}
    J -->|Yes - fullUpdates| K[Always update vehicle\nupdateManifestVehicle = true]
    J -->|No| L[Set vehicle only if\nmanifest currently has none\nFallback to driver assigned vehicle]
  end

 

  G --> M
  H --> M
  K --> M
  L --> M

 

  M{existingManifest found?} -->|Yes| N{clearExistingManifestLinks?\nName changed, date changed\nfullUpdates, or removeManifest}
  N -->|Yes| O[Remove all ManifestItems\nfor this order from old manifest\nSAVE\nAdd old manifest to updatedManifests\nmanifest = null]
  N -->|No| P[Reuse existingManifest\nas manifest]

 

  M -->|No - new order| Q[manifest = null\nwill be found or created below]

 

  O --> R
  P --> R
  Q --> R

 

  R{manifest == null\nand manifestName not empty?} -->|Yes| S[Determine manifest date\nfrom order DeliveryTime or PickupTime\nfallback to tomorrow]
  S --> T[Find Manifest by\nAccountId + name + date]
  T --> U{Found?}
  U -->|Yes| V[Use found manifest]
  U -->|No| W{CreateManifest\nor UpdateManifest\nor UpdateManifestComplete?}
  W -->|Yes| X[Create new Manifest\nAccountId, name, date\nDriverId, VehicleId if provided\ndb.Manifests.Add]
  W -->|No - AllocateToManifest only| Y[manifest = null\nallocation will be skipped]

 

  V --> Z
  X --> Z
  R -->|manifest already set| Z

 

  Z{manifest != null?} -->|No| END([Return null\nno manifest allocation])
  Z -->|Yes| AA[Apply driver and vehicle\nfullUpdates always overwrites\nbasicUpdates only fills empty slots\nSAVE]

 

  AA --> AB[Add manifest to updatedManifests]

 

  AB --> AC{zoneUpdated?\nZonal manifest already handled allocation}
  AC -->|Yes| AD[Skip ManifestItem creation\nzone manager already linked order]

 

  AC -->|No| AE[Capture existing sequence\nQueueNr, travel times, EarliestArrival\nfor position preservation]

 

  AE --> AF{targetShipment != null?}

 

  AF -->|Yes - shipment path| AG{ShipmentItem already\nexists on manifest?}
  AG -->|Yes| AH[Skip - already linked]
  AG -->|No| AI{Order TypeId == PickupDelivery?}
  AI -->|Yes| AJ[Add 2 ManifestItems\nShipmentId + IsPickup=true\nShipmentId + IsPickup=false\nSAVE]
  AI -->|No| AK[Add 1 ManifestItem\nShipmentId + IsPickup based on type\nSAVE]

 

  AF -->|No - direct order path| AL{Order TypeId == PickupDelivery?}
  AL -->|Yes| AM[Add 2 ManifestItems\nOrderId + IsPickup=true\nQueueNr from sequence or next\nOrderId + IsPickup=false\nSAVE]
  AL -->|No| AN[Add 1 ManifestItem\nOrderId\nIsPickup based on type\nQueueNr from sequence or ManifestPickupSequence or ManifestDeliverySequence\npreserved TravelSeconds and TravelMetres\nSAVE]

 

  AH --> AO
  AJ --> AO
  AK --> AO
  AM --> AO
  AN --> AO
  AD --> AO

 

  AO{Beemart integration\napplies to account?} -->|Yes| AP[SendOrderDetailsToBeemart]
  AO -->|No| AQ
  AP --> AQ([Return manifest])

4 · Input Flag Reference

All boolean/string flags on the request payload and what they control.

Container-level flags (OrdersWithItemsModel)

FlagTypeEffect
ConsolidateOrdersbool?Enables shipment consolidation. Orders with same type, client, address, and date are grouped into a Shipment. ManifestItems reference the Shipment, not individual orders.
CreateManifestboolCreates a new Manifest if one with matching name+date doesn't exist. Has no effect without a manifest name.
AllocateToManifestboolAllocates to an existing Manifest by name+date. Does not create one. Used for new orders only.
UpdateManifestbool?Basic manifest updates for existing orders. Sets driver/vehicle only if manifest currently has none. Clears old manifest links if name or date changed.
UpdateManifestCompletebool?Full manifest replacement for existing orders. Always overwrites driver and vehicle. Always clears existing ManifestItem links and re-creates them.
IgnoreCustomerReferenceMatchbool?Skips the CustomerReference lookup when finding existing orders. Forces new-order creation when Id and OrderNumber also don't match.
AppendNewOrderDetailsbool?Prepends incoming OrderDetails to existing text instead of overwriting. Skips if text is already present.
ManifestNumberstringFallback manifest name used for all orders in the container and for break allocation.
DriverNamestringFallback driver name for manifest assignment. Per-order DriverName takes precedence.
VehicleNamestringFallback vehicle name for manifest assignment. Per-order VehicleName takes precedence.

Per-order flags (OrderWithItemsModel)

FlagTypeEffect
Idint?Primary lookup key for existing order. Takes priority over OrderNumber and CustomerReference.
OrderNumberstringSecondary lookup key. Used if Id not supplied or not found.
CustomerReferencestringTertiary lookup key. Skipped if IgnoreCustomerReferenceMatch is set.
StatusIdint?Cancelled (5) → cancel the order (must be Pending or already Cancelled).
Pending (0) → reactivate a cancelled order.
      Any other status on a cancelled order → returns 422.
ManifestNamestringPer-order manifest name. Overrides container-level ManifestNumber. Value ** removes the order from its current manifest.
ManifestPickupSequenceint?Explicit QueueNr for the pickup ManifestItem. Falls back to preserved sequence then next available.
ManifestDeliverySequenceint?Explicit QueueNr for the delivery ManifestItem.
DriverNamestringDriver to assign to the manifest. Matched by full name or username. Takes precedence over container-level DriverName.
VehicleNamestringVehicle to assign to the manifest. Matched by name. Falls back to driver's assigned vehicle if not supplied.

5 · Key Behaviours and Edge Cases

In-progress order entity writes: OrderItems, Barcodes, Units, and Attributes are only rewritten when order.StatusId == Pending.   For in-progress orders, updateOrderEntities = false — child entities are left untouched even if supplied in the payload.
Shipment dissolution: If an order is removed from a shipment and only one order remains, the shipment is dissolved:   the remaining order's ManifestItems are converted back from ShipmentIdOrderId. If zero remain, the ManifestItems are deleted.
Manifest de-allocation (ManifestName = "**"): Setting the manifest name to the literal string ** removes   all ManifestItems for the order and does not create a new manifest.
UpdateManifest vs UpdateManifestComplete: UpdateManifest is additive — it only assigns a driver/vehicle if none exists and   only clears links if the manifest name or date changes. UpdateManifestComplete always replaces driver/vehicle and always clears and recreates   ManifestItem links, regardless of what changed.
Triton override: If a Triton integration is active and the order's existing manifest passes the Triton check,   the entire order update is bypassed — only barcodes are saved. This is an integration-specific short-circuit that silently   ignores all other incoming data.
v1 vs v3 error handling: The v1 endpoint (saveOrdersWithItems) throws exceptions on most errors;   the v3 endpoint (save-orders-with-items) returns structured ApiResult with 422 status codes.   Both call identical business logic with returnSmartResponse controlling the error path.

6 · Key File References

MethodFile : LineNotes
saveOrdersWithItems()Controllers/OrdersController.cs:1430v1 entry point — APIKey auth, always 200
ImportOrdersWithItems()Controllers/OrdersController.cs:1452v3 entry point — APIKey auth, returns ApiResult with HTTP status
SaveOrdersInternal() (outer)Controllers/OrdersController.cs:1474Starts import history record, delegates to inner overload
SaveOrdersInternal() (inner)Controllers/OrdersController.cs:1517All business logic — order loop, breaks, UpdateManifests
AddOrUpdateClient()Controllers/OrdersController.cs:2586Geocodes address, creates or updates Client entity
SaveOrderItems()Controllers/OrdersController.cs:2776Rewrites OrderItems + ItemSerials, updates order weight/volume/price
SaveUnits()OrdersController.csRewrites UnitsInOrders
SaveAttributes()Controllers/OrdersController.cs:2544Rewrites OrderAttributes
SaveBarcodes()Controllers/OrdersController.cs:2567Appends new barcodes (never overwrites existing)
ProcessShipmentConsolidation()Controllers/OrdersController.cs:3576Finds/creates Shipment, dissolves if needed
ConvertOrderManifestItemsToShipment()Controllers/OrdersController.cs:3790Flips ManifestItem from OrderId to ShipmentId
SaveManifest()Controllers/OrdersController.cs:3136Find/create manifest, manage driver/vehicle, write ManifestItems
UpdateManifests()Controllers/OrdersController.cs:73Post-loop: triggers ManifestManager recalculation for all touched manifests
ManifestManager.AllocateToZonalManifest()Managers/ManifestManager.csZonal manifest logic for new orders without explicit manifest name
ManifestManager.UpdateManifests()Managers/ManifestManager.csRecalculates totals, distances, durations for each affected manifest

    • Related Articles

    • SolBox API Integration Reference Guide

      SolBox API Integration Reference Guide Version 1.8 SolBox API Integration Reference Guide is your “single source of truth” for connecting external systems to SolBox and keeping orders, runs, and delivery outcomes perfectly in sync. This guide focuses ...
    • Standard Functional Specification and Implementation Scope

      1. Operational Process Flow An analysis will be completed of the current processes. A flowchart has been created to show standard future processes and the stages of the implementation. 1.1. Process Description Following is the description of steps in ...
    • Route Optimisation Settings

      This guide will help select the optimisation plan that best suits routing preferences by configuring the optimiser algorithm and route structure, Accessing and Configuring Route Optimisation 1. Go to the Settings menu and click on the Route ...
    • SolBox SmartMove Release Notes

      SolBox SmartMove Release Notes We’re committed to continuously improving our software to provide you with the best possible experience. This page keeps you informed about the latest updates, new features, enhancements, and bug fixes in our platform. ...
    • New SmartMove app

      Main page Click "Start Delivery" Assigned Manifest page You will see "Assigned Manifests" page with 2 menus: "TODAY" which is today manifest and "ALL" which is all manifests that this user has been assigned before. Click into the manifest that you ...