🚚 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