production

This commit is contained in:
2026-08-28 11:13:15 +05:30
parent d7348e253f
commit 5723d373b2
162 changed files with 17924 additions and 7026 deletions

View File

@@ -1,5 +1,6 @@
import 'package:miler/Models/stop_status.dart';
import 'package:miler/views/Dashboard/pickups/stop_type.dart';
import 'package:miler/data/pickup_locations.dart';
import 'package:miler/data/service_profile.dart';
/// ─────────────────────────────────────────────────────────────────────────
@@ -136,14 +137,33 @@ class MilkRun {
/// prevent, produced by the two halves of the decision disagreeing.
///
/// One key list, one answer.
static String sourceNameOf(Map<String, dynamic> stop) =>
(stop['sourcename'] ??
stop['SourceName'] ??
stop['kitchenname'] ??
stop['KitchenName'] ??
'')
.toString()
.trim();
///
/// ── The tenant's own location list wins ──
///
/// The booking row's `sourcename` is filled by the backend from
/// `providercompany` / `providerlocation`, and what lands there is often
/// whoever is on the account rather than the counter — the route card's
/// heading, the biggest type on Home, read `Sudharsan`.
///
/// The booking does carry the location's **id**, and the tenant's locations
/// are a list with proper names on them, so the name is joined rather than
/// read off the row. See [PickupLocations]: when the table is empty — not
/// loaded yet, or the rider's token is not admitted to the admin route —
/// this falls through to exactly what it returned before.
static String sourceNameOf(Map<String, dynamic> stop) {
final resolved = PickupLocations.nameFor(
stop['sourceid'] ?? stop['kitchenid'] ?? stop['pickuplocationid'],
);
if (resolved.isNotEmpty) return resolved;
return (stop['sourcename'] ??
stop['SourceName'] ??
stop['kitchenname'] ??
stop['KitchenName'] ??
'')
.toString()
.trim();
}
/// True when two stops are collected from the same counter.
static bool sameSource(Map<String, dynamic> a, Map<String, dynamic> b) =>
@@ -300,8 +320,8 @@ class MilkRun {
.trim();
return name.isEmpty ? 'customer' : name;
}
final kitchen = sourceNameOf(stop);
if (kitchen.isNotEmpty) return kitchen;
final place = pickupPlaceOf(stop);
if (place.isNotEmpty) return place;
// ── The word "pickup" is never a place ──
//
@@ -313,11 +333,31 @@ class MilkRun {
// promoted to a title.
//
// A rider thinks in PLACES, and the payload still knows one: the pickup
// address. Its first non-numeric component is the neighbourhood — the
// same reading the timeline's area line uses — and "RS Puram" is
// something he can ride to in a way "pickup" is not. Only when the
// payload has no address either does the label fall back to a word, and
// then it is at least a capitalised noun.
// address. Only when it has no address either does the label fall back to
// a word, and then it is at least a capitalised noun. See [pickupPlaceOf].
return 'Pickup';
}
/// **Where the rider is collecting from**, or `''` when the payload says.
///
/// ── Why this is not the customer's name ──
///
/// The route card's heading is the biggest type on Home, and on a group of
/// one order it was the *customer* — so a rider planning his next collection
/// read `Sudharsan`, which is a person, not somewhere he can ride to. The
/// place was on the card the whole time, one line down, in grey.
///
/// The order of preference is the order of usefulness at a kerb:
///
/// 1. the counter's own name — what is written on the sign
/// 2. the neighbourhood off the pickup address — what he steers by
///
/// Split out of [navigationLabel] so a caller can tell "no place in this
/// payload" from the word *Pickup*, which reads as a name and is not one.
static String pickupPlaceOf(Map<String, dynamic> stop) {
final kitchen = sourceNameOf(stop);
if (kitchen.isNotEmpty) return kitchen;
for (final key in const ['pickupaddress', 'PickupAddress']) {
final raw = (stop[key] ?? '').toString().trim();
if (raw.isEmpty) continue;
@@ -333,7 +373,7 @@ class MilkRun {
return p.replaceFirst(RegExp(r'^[0-9][0-9/\-]*\s+'), '');
}
}
return 'Pickup';
return '';
}
/// Stops still owing the rider a collection at a given counter.
@@ -387,6 +427,23 @@ class MilkRun {
required Set<String> collectedIds,
Set<String> outForDeliveryIds = const {},
Set<String> deliveredIds = const {},
/// Stops the rider has reported arriving at.
///
/// ── Why arrival is a set and not a status ──
///
/// The backend team confirmed there is no Arrived rung in the booking
/// lifecycle: `/reached` records the arrival as a **timestamp**
/// (`reachedat`) beside a status that stays `pickup_scheduled`. And
/// `GET /miler/bookings` does not return that stamp — see
/// `getArrivedOrderIds` — so there is no server field to reconstruct
/// arrival from after a refresh.
///
/// It therefore arrives the same way the other two rider-owned facts do,
/// as a set of ids, and it is subject to the same precedence: it speaks
/// only where nothing further along has happened. Delete this parameter,
/// not the rung, on the day the stamp appears on a booking row.
Set<String> arrivedIds = const {},
}) {
final id = idOf(stop);
final reported = stopStatusOf(stop);
@@ -422,7 +479,15 @@ class MilkRun {
reported == StopStatus.outForDelivery) {
return StopStatus.picked;
}
if (reported == StopStatus.arrived) return StopStatus.arrived;
// ── Arrived, from either source ──
//
// Below everything above it, deliberately: the pickup milestone outranks
// the arrival, and the delivery rungs outrank both. A rider's own arrival
// record can never walk a stop the hub has moved on backwards — the same
// rule the local store obeys everywhere else in the app.
if (reported == StopStatus.arrived || arrivedIds.contains(id)) {
return StopStatus.arrived;
}
if (acceptedIds.contains(id) || reported == StopStatus.accepted) {
return StopStatus.accepted;
}