device infos

This commit is contained in:
2026-08-28 15:07:30 +05:30
parent 5723d373b2
commit 69f4f3e909
84 changed files with 4337 additions and 2339 deletions

View File

@@ -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 {