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.
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])
SaveManifest():(CreateManifest OR AllocateToManifest) + manifest name supplied(UpdateManifest == true OR UpdateManifestComplete == true)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])
TypeIdSaveManifest() 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])
All boolean/string flags on the request payload and what they control.
OrdersWithItemsModel)| Flag | Type | Effect |
|---|---|---|
ConsolidateOrders | bool? | Enables shipment consolidation. Orders with same type, client, address, and date are grouped into a Shipment. ManifestItems reference the Shipment, not individual orders. |
CreateManifest | bool | Creates a new Manifest if one with matching name+date doesn't exist. Has no effect without a manifest name. |
AllocateToManifest | bool | Allocates to an existing Manifest by name+date. Does not create one. Used for new orders only. |
UpdateManifest | bool? | Basic manifest updates for existing orders. Sets driver/vehicle only if manifest currently has none. Clears old manifest links if name or date changed. |
UpdateManifestComplete | bool? | Full manifest replacement for existing orders. Always overwrites driver and vehicle. Always clears existing ManifestItem links and re-creates them. |
IgnoreCustomerReferenceMatch | bool? | Skips the CustomerReference lookup when finding existing orders. Forces new-order creation when Id and OrderNumber also don't match. |
AppendNewOrderDetails | bool? | Prepends incoming OrderDetails to existing text instead of overwriting. Skips if text is already present. |
ManifestNumber | string | Fallback manifest name used for all orders in the container and for break allocation. |
DriverName | string | Fallback driver name for manifest assignment. Per-order DriverName takes precedence. |
VehicleName | string | Fallback vehicle name for manifest assignment. Per-order VehicleName takes precedence. |
OrderWithItemsModel)| Flag | Type | Effect |
|---|---|---|
Id | int? | Primary lookup key for existing order. Takes priority over OrderNumber and CustomerReference. |
OrderNumber | string | Secondary lookup key. Used if Id not supplied or not found. |
CustomerReference | string | Tertiary lookup key. Skipped if IgnoreCustomerReferenceMatch is set. |
StatusId | int? | 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. |
ManifestName | string | Per-order manifest name. Overrides container-level ManifestNumber. Value ** removes the order from its current manifest. |
ManifestPickupSequence | int? | Explicit QueueNr for the pickup ManifestItem. Falls back to preserved sequence then next available. |
ManifestDeliverySequence | int? | Explicit QueueNr for the delivery ManifestItem. |
DriverName | string | Driver to assign to the manifest. Matched by full name or username. Takes precedence over container-level DriverName. |
VehicleName | string | Vehicle to assign to the manifest. Matched by name. Falls back to driver's assigned vehicle if not supplied. |
order.StatusId == Pending.
For in-progress orders, updateOrderEntities = false — child entities are left untouched even if supplied in the payload.ShipmentId → OrderId. If zero remain, the ManifestItems are deleted.ManifestName = "**"): Setting the manifest name to the literal string ** removes
all ManifestItems for the order and does not create a new manifest.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.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.| Method | File : Line | Notes |
|---|---|---|
saveOrdersWithItems() | Controllers/OrdersController.cs:1430 | v1 entry point — APIKey auth, always 200 |
ImportOrdersWithItems() | Controllers/OrdersController.cs:1452 | v3 entry point — APIKey auth, returns ApiResult with HTTP status |
SaveOrdersInternal() (outer) | Controllers/OrdersController.cs:1474 | Starts import history record, delegates to inner overload |
SaveOrdersInternal() (inner) | Controllers/OrdersController.cs:1517 | All business logic — order loop, breaks, UpdateManifests |
AddOrUpdateClient() | Controllers/OrdersController.cs:2586 | Geocodes address, creates or updates Client entity |
SaveOrderItems() | Controllers/OrdersController.cs:2776 | Rewrites OrderItems + ItemSerials, updates order weight/volume/price |
SaveUnits() | OrdersController.cs | Rewrites UnitsInOrders |
SaveAttributes() | Controllers/OrdersController.cs:2544 | Rewrites OrderAttributes |
SaveBarcodes() | Controllers/OrdersController.cs:2567 | Appends new barcodes (never overwrites existing) |
ProcessShipmentConsolidation() | Controllers/OrdersController.cs:3576 | Finds/creates Shipment, dissolves if needed |
ConvertOrderManifestItemsToShipment() | Controllers/OrdersController.cs:3790 | Flips ManifestItem from OrderId to ShipmentId |
SaveManifest() | Controllers/OrdersController.cs:3136 | Find/create manifest, manage driver/vehicle, write ManifestItems |
UpdateManifests() | Controllers/OrdersController.cs:73 | Post-loop: triggers ManifestManager recalculation for all touched manifests |
ManifestManager.AllocateToZonalManifest() | Managers/ManifestManager.cs | Zonal manifest logic for new orders without explicit manifest name |
ManifestManager.UpdateManifests() | Managers/ManifestManager.cs | Recalculates totals, distances, durations for each affected manifest |