Files
doormile_milderapp/lib/Models/stop_status.dart
2026-08-11 13:16:33 +05:30

114 lines
3.9 KiB
Dart

/// Canonical lifecycle status for a booking/stop, parsed from the backend's
/// free-form `orderstatus` string.
///
/// Status used to be compared as raw string literals in ~90 places, with
/// case-sensitivity landmines (`'active'` vs `'ACTIVE'`) and variant spellings
/// (`'picked'` / `'picked up'` / `'pickuped'`). This normalizes all of that in
/// one place so filters agree everywhere.
///
/// NOTE: this is the *read* side. The status-change API still sends its own
/// protocol verbs (`ACCEPTED`/`ARRIVED`/`PICKED`/`REJECTED`/`CANCELLED`) — see
/// [OrderAction] for those canonical write values.
enum StopStatus {
newStop,
assigned,
accepted,
active,
arrived,
picked,
skipped,
cancelled,
rejected,
unknown,
}
/// Normalizes any raw `orderstatus` value (case-insensitive, trimmed, variant
/// spellings folded) into a [StopStatus].
StopStatus stopStatusFromRaw(dynamic raw) {
final s = (raw?.toString() ?? '').trim().toLowerCase();
switch (s) {
case 'new':
return StopStatus.newStop;
case 'assigned':
case 'miler_assigned':
case 'pending':
return StopStatus.assigned;
case 'accepted':
return StopStatus.accepted;
case 'active':
return StopStatus.active;
case 'arrived':
return StopStatus.arrived;
case 'picked':
case 'picked up':
case 'pickuped':
case 'pickedup':
return StopStatus.picked;
case 'skipped':
return StopStatus.skipped;
case 'cancelled':
case 'canceled':
return StopStatus.cancelled;
case 'rejected':
return StopStatus.rejected;
default:
return StopStatus.unknown;
}
}
/// Parse straight from a booking map's `orderstatus`.
StopStatus stopStatusOf(Map<String, dynamic> booking) =>
stopStatusFromRaw(booking['orderstatus']);
extension StopStatusX on StopStatus {
bool get isPicked => this == StopStatus.picked;
bool get isCancelled => this == StopStatus.cancelled;
bool get isSkipped => this == StopStatus.skipped;
bool get isActive => this == StopStatus.active;
bool get isRejected => this == StopStatus.rejected;
/// A completed pickup — picked up or cancelled. (Matches the long-standing
/// `!= 'picked' && != 'picked up' && != 'cancelled'` filter used to build the
/// "next stops" and remaining-pickups lists.)
bool get isFinishedPickup =>
this == StopStatus.picked || this == StopStatus.cancelled;
/// Still awaiting the rider's acceptance — belongs on Home, not Bookings.
bool get isPending =>
this == StopStatus.newStop || this == StopStatus.assigned;
/// What this state is *called*, in the rider's words.
///
/// The status vocabulary had been re-invented per widget: the live banner had
/// its own switch over raw `orderstatus` strings returning "At pickup" and
/// "In progress", the trip card wrote 'Accepted' as a literal, and the two
/// disagreed about the same backend state. A rider moving between Home, the
/// banner and Bookings was reading three vocabularies for one lifecycle and
/// re-learning the app at each stop.
///
/// One list, here, next to the parser that produces the states — so a new
/// status cannot be added without someone deciding what to call it.
String get label => switch (this) {
StopStatus.newStop => 'New',
StopStatus.assigned => 'Assigned',
StopStatus.accepted => 'Accepted',
StopStatus.active => 'In progress',
StopStatus.arrived => 'At the stop',
StopStatus.picked => 'Completed',
StopStatus.skipped => 'Skipped',
StopStatus.cancelled => 'Cancelled',
StopStatus.rejected => 'Rejected',
StopStatus.unknown => 'Active',
};
}
/// Canonical protocol verbs sent to the status-change API (the *write* side).
class OrderAction {
static const String accept = 'ACCEPT';
static const String accepted = 'ACCEPTED';
static const String arrived = 'ARRIVED';
static const String picked = 'PICKED';
static const String rejected = 'REJECTED';
static const String cancelled = 'CANCELLED';
}