device infos
This commit is contained in:
@@ -176,8 +176,100 @@ extension ConsignmentStateX on ConsignmentState {
|
||||
/// widened it on 21 Aug 2026 so a failed attempt is reportable the moment
|
||||
/// the parcel is collected, not only once the round has started.
|
||||
bool get canSkip => needsRelease || isDeliverable;
|
||||
|
||||
/// Why **Start ride** could not set off, in words a rider can act on — or
|
||||
/// null when this state is not a reason to refuse one.
|
||||
///
|
||||
/// ── The screen was reading the state machine out loud ──
|
||||
///
|
||||
/// When `start-delivery` refuses, the backend answers with an assertion
|
||||
/// about its own model: *"consignment is Inwarded_At_Hub, not
|
||||
/// Collected_By_Miler"*. That sentence was passed through to the rider
|
||||
/// verbatim, on the reasoning that the server's own words beat a guess — and
|
||||
/// that reasoning is right about *whose* answer it is and wrong about *what
|
||||
/// kind of sentence* it is. `Inwarded_At_Hub` is a database enum. It is not
|
||||
/// something a man holding a phone at a kitchen counter can do anything
|
||||
/// with, and he had no way to tell it from a bug.
|
||||
///
|
||||
/// So the state is translated here, in the file that owns the vocabulary,
|
||||
/// exactly once. Every sentence answers the only two questions he has: is
|
||||
/// this mine, and is sliding again going to help.
|
||||
///
|
||||
/// Null for [outForDelivery], [collectedByMiler] and [unknown] — the first
|
||||
/// two are not refusals at all and the third is not an answer, so the caller
|
||||
/// falls back rather than inventing a cause.
|
||||
String? get startRefusalSentence => switch (this) {
|
||||
// Converted and not yet routed. The one refusal here that a second slide
|
||||
// in a minute genuinely can clear.
|
||||
ConsignmentState.created =>
|
||||
'Your office is still setting this one up. Give it a moment and slide '
|
||||
'again.',
|
||||
// The logistics network has it. Said without naming a building the rider
|
||||
// has never been to — what matters is that it is not his round.
|
||||
ConsignmentState.inwardedAtHub ||
|
||||
ConsignmentState.tripsheetLoaded ||
|
||||
ConsignmentState.inTransit =>
|
||||
"This one has already been handed on — it's not yours to deliver.",
|
||||
ConsignmentState.delivered =>
|
||||
'This one has already been delivered. Nothing left to start.',
|
||||
ConsignmentState.rtoInitiated || ConsignmentState.returnedToSender =>
|
||||
'This one is going back to the sender, so there is no delivery to '
|
||||
'start.',
|
||||
ConsignmentState.missing || ConsignmentState.damaged =>
|
||||
'Your office has flagged a problem with this parcel. They have to clear '
|
||||
'it before it can go out.',
|
||||
ConsignmentState.cancelled =>
|
||||
'This one has been cancelled. There is nothing to deliver.',
|
||||
ConsignmentState.outForDelivery ||
|
||||
ConsignmentState.collectedByMiler ||
|
||||
ConsignmentState.unknown => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// The consignment state a **start-delivery** refusal names, or
|
||||
/// [ConsignmentState.unknown] when it names none this build knows.
|
||||
///
|
||||
/// ── Reading a state out of a sentence, safely ──
|
||||
///
|
||||
/// Parsing prose is normally a bad idea, and this is the case where it is not,
|
||||
/// because it does not depend on the prose. `POST
|
||||
/// /miler/consignments/:id/start-delivery` accepts **exactly one** state —
|
||||
/// `Collected_By_Miler`. So of the state names a refusal from that endpoint
|
||||
/// mentions, the only one that cannot be describing where the consignment
|
||||
/// actually *is* is the one that would have been accepted. Drop that, and
|
||||
/// whatever is left is the answer — whichever order the words come in, and
|
||||
/// however the sentence is later reworded.
|
||||
///
|
||||
/// This exists because the state read can fail twice: the row need not carry
|
||||
/// `consignmentstatus`, and `GET /miler/consignments/:id` can 404 on an older
|
||||
/// deployment. In that case the refusal itself is the only thing that knows
|
||||
/// what happened, and throwing it away costs the rider a real answer.
|
||||
///
|
||||
/// Matches only the wire's own shape — capitalised words joined by
|
||||
/// underscores — so an ordinary sentence yields nothing.
|
||||
ConsignmentState consignmentStateNamedInRefusal(String message) {
|
||||
if (message.isEmpty) return ConsignmentState.unknown;
|
||||
for (final m in RegExp(
|
||||
r'[A-Za-z]+(?:_[A-Za-z]+)+',
|
||||
).allMatches(message)) {
|
||||
final state = consignmentStateFromRaw(m.group(0));
|
||||
if (state == ConsignmentState.unknown) continue;
|
||||
// The state the endpoint requires, not the state it found.
|
||||
if (state == ConsignmentState.collectedByMiler) continue;
|
||||
return state;
|
||||
}
|
||||
return ConsignmentState.unknown;
|
||||
}
|
||||
|
||||
/// True when [message] is the backend talking to itself.
|
||||
///
|
||||
/// A refusal carrying a wire enum — `Inwarded_At_Hub`, `Out_for_Delivery` — is
|
||||
/// a state-machine assertion, not a sentence for a rider, and it must never
|
||||
/// reach a screen. Anything else the server says is prose written for a person
|
||||
/// and is worth more than a generic apology, so it is still passed through.
|
||||
bool namesWireState(String message) =>
|
||||
RegExp(r'[A-Za-z]+(?:_[A-Za-z]+)+').hasMatch(message);
|
||||
|
||||
/// What the app is allowed to do with a consignment, decided from the
|
||||
/// authoritative server state rather than from a booking row or a local flag.
|
||||
enum DeliverGate {
|
||||
|
||||
Reference in New Issue
Block a user