production
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:miler/views/Dashboard/pickups/route_metrics.dart';
|
||||
import 'package:miler/data/route_order.dart';
|
||||
import 'package:miler/data/service_profile.dart';
|
||||
import 'package:miler/Models/stop_status.dart';
|
||||
import 'package:miler/data/work_domain.dart';
|
||||
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// A TRIP — one slot's worth of work, as a single unit.
|
||||
@@ -105,6 +106,16 @@ enum DayPart {
|
||||
///
|
||||
/// Trip 2 is the afternoon even on a day with no morning work — the number is
|
||||
/// the day part, so the rider's "Trip 2" and the hub's are the same run.
|
||||
///
|
||||
/// ── This split is the client's, and the backend has confirmed it ──
|
||||
///
|
||||
/// Asked directly (24 Aug 2026): *does the console own Trip 1/2/3?* Answer:
|
||||
/// **no — there is no `tripid` or `slotid` backend-side.** The day-part
|
||||
/// grouping is therefore documented client behaviour rather than a guess
|
||||
/// standing in for a field that exists, and the app is not waiting on one.
|
||||
///
|
||||
/// If that ever changes, this is the class to delete: the ids would arrive on
|
||||
/// the booking row and the split would stop being derived at all.
|
||||
extension TripSlots on List<Trip> {
|
||||
/// The day laid out as slots: index 0 is the morning, 1 the afternoon, 2 the
|
||||
/// evening, and an empty slot is a part of the day with no work in it.
|
||||
@@ -212,6 +223,61 @@ class Trip {
|
||||
|
||||
int get stopCount => stops.length;
|
||||
|
||||
/// The same trip, recounted over **only the work still owed on Home**.
|
||||
///
|
||||
/// ── Why the brief was reading the wrong number ──
|
||||
///
|
||||
/// The four figures in [TripBriefStrip] — duration, distance, parcels,
|
||||
/// payment — were computed once, over every stop the slot ever held, and
|
||||
/// never moved again. The headline above them says `20 stops left` and
|
||||
/// counts down as the rider works; the figures under it kept describing the
|
||||
/// morning he started with.
|
||||
///
|
||||
/// So a rider with five collections to go read `≈4h 15m · 29.0 km · 25
|
||||
/// parcels` — the whole day, including the twenty stops already in his box
|
||||
/// and gone to Deliveries. The one question the card exists to answer, *what
|
||||
/// is left in front of me*, was the one thing on it that was stale.
|
||||
///
|
||||
/// ── Where the line is ──
|
||||
///
|
||||
/// [WorkBoundary.pickupComplete] — the same boundary Home and Deliveries
|
||||
/// already split on, so a stop cannot be counted here and owned there. Once
|
||||
/// the collection is done the stop belongs to the delivery leg and is not
|
||||
/// this card's business. Skipped and cancelled work is dropped for the same
|
||||
/// reason: it is a record now, not a stop to plan around.
|
||||
///
|
||||
/// [origin] is the point the round trip is measured from — the rider's own
|
||||
/// position, which is what the page passes to [Trip.fromStops] as the hub.
|
||||
/// Passing it keeps the recount on the same footing as the original
|
||||
/// measurement; omitting it would silently fall back to per-stop kilometres
|
||||
/// and read as a distance that changed for no reason.
|
||||
///
|
||||
/// Returns `this` unchanged when nothing has been collected yet, so the
|
||||
/// common case allocates nothing.
|
||||
Trip outstanding({
|
||||
Set<String> collectedIds = const <String>{},
|
||||
double? originLat,
|
||||
double? originLng,
|
||||
}) {
|
||||
final left = <Map<String, dynamic>>[
|
||||
for (final s in stops)
|
||||
if (!WorkBoundary.pickupComplete(s, collectedIds: collectedIds) &&
|
||||
!stopStatusOf(s).isCancelled &&
|
||||
!stopStatusOf(s).isSkipped)
|
||||
s,
|
||||
];
|
||||
if (left.length == stops.length) return this;
|
||||
|
||||
return Trip.fromStops(
|
||||
id: id,
|
||||
stops: left,
|
||||
slotStart: slotStart,
|
||||
slotEnd: slotEnd,
|
||||
hubLat: originLat,
|
||||
hubLng: originLng,
|
||||
);
|
||||
}
|
||||
|
||||
/// The number the rider is actually planning around: ride time plus every
|
||||
/// minute spent standing at a door.
|
||||
Duration get totalDuration => travelDuration + serviceDuration;
|
||||
@@ -847,6 +913,24 @@ enum StopState {
|
||||
/// [ServiceProfile.handoffAt] and the collected store.
|
||||
collected,
|
||||
|
||||
/// He is **at** the stop and has said so — the arrival is recorded, the
|
||||
/// pickup is not yet made.
|
||||
///
|
||||
/// ── Why this is not [active] ──
|
||||
///
|
||||
/// It was. `stopStateOf` folded `arrived` and `active` into one value, so the
|
||||
/// Home row had no arrived to render and the live chip printed the literal
|
||||
/// word **Active** the moment a rider slid *confirm arrival*. He was told his
|
||||
/// arrival had not registered, when in fact it had: the rung moved, the API
|
||||
/// fired, the store was written, and the one word on screen said something
|
||||
/// else.
|
||||
///
|
||||
/// They are different facts. Arrived is a rung on the pickup ladder that the
|
||||
/// rider puts the stop on. Active is an operational statement about which
|
||||
/// stop is being worked — see `_activePickupOrderId` — and it is the word the
|
||||
/// backend reserves for a delivery under way.
|
||||
arrived,
|
||||
|
||||
/// The rider is physically on this stop right now.
|
||||
active,
|
||||
|
||||
@@ -874,12 +958,35 @@ extension StopStateX on StopState {
|
||||
bool get isCommitted =>
|
||||
this == StopState.accepted ||
|
||||
this == StopState.collected ||
|
||||
this == StopState.active ||
|
||||
this.isAtSource ||
|
||||
this == StopState.skipped;
|
||||
|
||||
/// In the rider's hands right now.
|
||||
bool get isCollected => this == StopState.collected;
|
||||
|
||||
/// He is **at** the source and the pickup is not yet made.
|
||||
///
|
||||
/// ── Why this is a predicate and not a comparison ──
|
||||
///
|
||||
/// [StopState.arrived] and [StopState.active] were one value. Splitting them
|
||||
/// apart is what finally let the row say *Arrived* instead of *Active* — and
|
||||
/// it silently changed the answer at every site that had written
|
||||
/// `== StopState.active` to mean **is he standing at the counter**. There
|
||||
/// were a dozen, and each of them began answering `false` for the one rung
|
||||
/// whose entire point is that he is standing there:
|
||||
///
|
||||
/// • Home's visibility filter — so a stop *left the screen* the moment he
|
||||
/// marked it arrived, and Deliveries would not take it either because
|
||||
/// arrival is pre-pickup. The stop was in neither place.
|
||||
/// • the selection bar's action, which offered *Accept* for a stop he was
|
||||
/// already at
|
||||
/// • the card's own **Mark as picked** button, which stopped being built
|
||||
/// • which group opens by default, and which one counts as live
|
||||
///
|
||||
/// One question asked in one place, so the next rung added to the ladder
|
||||
/// cannot quietly repeat it.
|
||||
bool get isAtSource => this == StopState.arrived || this == StopState.active;
|
||||
|
||||
/// What this state is called on a card.
|
||||
///
|
||||
/// ── Two vocabularies, one state machine ──
|
||||
@@ -899,6 +1006,7 @@ extension StopStateX on StopState {
|
||||
StopState.pending => 'Awaiting your decision',
|
||||
StopState.accepted => 'Accepted',
|
||||
StopState.collected => 'Collected',
|
||||
StopState.arrived => 'Arrived',
|
||||
StopState.active => 'In progress',
|
||||
StopState.done => 'Completed',
|
||||
StopState.skipped => 'Skipped',
|
||||
@@ -908,6 +1016,7 @@ extension StopStateX on StopState {
|
||||
StopState.pending => 'Pending',
|
||||
StopState.accepted => 'Accepted',
|
||||
StopState.collected => 'Collected',
|
||||
StopState.arrived => 'Arrived',
|
||||
StopState.active => 'Out for delivery',
|
||||
StopState.done => 'Delivered',
|
||||
StopState.skipped => 'Skipped',
|
||||
@@ -946,8 +1055,11 @@ StopState stopStateOf(
|
||||
return StopState.done;
|
||||
}
|
||||
|
||||
// 2. The rider is physically on the stop — also not a local decision.
|
||||
if (raw == 'active' || raw == 'arrived') return StopState.active;
|
||||
// 2. He is at the stop — also not a local decision.
|
||||
//
|
||||
// Two values, not one. Folding them lost the arrival: see [StopState.arrived].
|
||||
if (raw == 'arrived') return StopState.arrived;
|
||||
if (raw == 'active') return StopState.active;
|
||||
if (raw == 'skipped') return StopState.skipped;
|
||||
|
||||
// 2b. Released for delivery — which `pickup-complete` does by itself on
|
||||
|
||||
Reference in New Issue
Block a user