ZuloOne
English
Sign in
Menu

🚚 DeliveryTrip planned vs actual times

Updated

← Developer wiki For the user: what the interface shows.

The dictionary already held DeliverySchedule.DepartHour/Minute and DeliveryRouteStop.WindowFromMinutes/WindowToMinutes/DwellMinutes. The trip did not copy them — there was nothing to compare against.

The commands stamp the values, not OnAfterPost: Completed is isReadOnly, and a late header write is refused. ActualComplete is written in the same SaveDocumentAsync that moves the subtype.

An optional DateTime on a document is generated as DateTime, not DateTime?. MinValue brings SQL Server down. On an INSERT OnBeforeSave sets 1901-01-01 (the print threshold is Year >= 1902). On an UPDATE leave it alone: a partial bag does not carry the dates that did not change, and the clamp would overwrite an ActualDepart the command has just set.

The actual at each stop#

DeliveryTripLines.ArrivedAt / DepartedAt. This is where it differs from the header: on a table part an optional scalar is generated nullable — DateTime?, decimal?, int? (the neighbouring PlannedFromMinutes shows it). On a document it is the opposite, non-nullable — hence the paragraph about 1901-01-01 above. Both statements are true at once, and it is the contrast that catches you: == default on a row does not mean "empty", while is DateTime x on a header is always true.

The practical consequence is direct. Were ArrivedAt non-nullable, an empty mark would equal 0001-01-01, that is midnight, and a stop with a 09:00–10:00 window would report "540 min early" — for every unmarked trip. That is nailed down by the test "a stop with a window but no mark yields no verdict".

IDeliveryService gained three methods:

Method Answer
StopFactProblem(trip) the refusal text or null — synchronous, the rows are already on the document
WindowVerdict(line) in the window / N min early / N min late / null
LateStopsSummaryAsync(tripId) a line for the dispatcher, or empty

Only what is filled in gets checked: otherwise the feature would block every current trip, none of which carry marks. StopFactProblem sits inside ValidateTripAsync, so one door covers both dispatch and completion.

LateStopsSummaryAsync is called in CompleteTripCommand before the move to Completed: the subtype is isReadOnly, and after the transition the rows can no longer be re-read.

The window is minutes from midnight of the delivery date, so the time of day is compared (arrived.Hour * 60 + arrived.Minute) rather than the full date: a trip running past midnight would otherwise produce a negative difference.

Stop coordinates#

The planned pin lives in DeliveryRouteStop.Lat / Lng. Both zero means there is no pin (as with a visit without coordinates): zero on one axis is valid, it is Greenwich or the equator. FillTripFromRouteAsync copies the pair to the line's PlannedLat / PlannedLng. The actual is ActualLat / ActualLng, filled manually. There is no live tracker.

DeliveryRoute.ArriveRadiusMeters: zero skips the distance check. Otherwise PinOffSummaryAsync computes the arc (Earth radius 6,371 km) and, if the actual is farther than allowed, reports the metres at completion. The trip still closes. Half a pair, or a coordinate outside −90…90 / −180…180, is a StopFactProblem refusal, not a silent skip.

The order of stops is not optimized: there is no road graph, so the sequence stays the route's Sequence.

Coverage — DeliveryFleetTest (19; 4 cover the per-stop actual and 5 cover the pin).

Same topic for other readers: Analyst · User

Where next#

Was this page helpful?