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

@@ -96,11 +96,62 @@ class UpdatePickupProvider {
/// a file this one has no business touching. What matters is that every
/// branch now ends in a real request or an honest failure — there are no
/// invented successes left.
/// The booking this payload is about, or null when it carries none.
///
/// Treats `0`, `''` and an unparseable value as absent — the three shapes a
/// missing id actually arrives in, none of which is the `null` a `??` chain
/// is looking for.
static Object? _bookingId(Map<String, dynamic> data) {
for (final key in const ['pickupid', 'orderheaderid']) {
final raw = data[key];
if (raw == null) continue;
final s = raw.toString().trim();
if (s.isEmpty) continue;
final n = int.tryParse(s);
if (n != null) {
if (n > 0) return n;
continue;
}
// A non-numeric reference — a tracking number — is a real id.
return s;
}
return null;
}
Future<Map<String, dynamic>?> updatePickup(Map<String, dynamic> data) async {
final String status = (data['orderstatus'] ?? '').toString();
final id = data['pickupid'] ?? data['orderheaderid'];
final String notes = (data['notes'] ?? '').toString();
// ── `??` was catching null, and a missing id is never null here ──
//
// This read `data['pickupid'] ?? data['orderheaderid']`. Every caller
// builds its payload with `int.tryParse('${'$'}{stop['pickupid'] ?? 0}') ?? 0`,
// so a row without a booking id arrives as **`0`** — which is not null, so
// the fallback never fired and the app posted to
// `/miler/bookings/0/reached`.
//
// Nothing complained. The rider's rung advanced anyway (arrival is his own
// report and is deliberately not gated on the write landing), so from the
// phone it looked identical to a healthy arrival — while the hub was told
// about booking zero, and the console showed no arrival at all.
//
// Zero and empty are *absent* now, and an absent id refuses the call
// outright rather than aiming it at a booking that does not exist. See
// [_bookingId].
final id = _bookingId(data);
if (id == null) {
ApiConfig.logGap(
'update:$status',
'no usable booking id on the payload — pickupid='
'${data['pickupid']}, orderheaderid=${data['orderheaderid']}. '
'Refusing to post; the hub would have been told about booking 0.',
);
return {
'status': false,
'message': 'This stop is missing its booking reference.',
};
}
final double? lat = double.tryParse(
'${data['riderslat'] ?? data['pickuplat'] ?? ''}',
);
@@ -199,6 +250,26 @@ class UpdatePickupProvider {
// mirror that flag — it reads which one happened.
final pivot = MilerLifecycle.pickupComplete(picked);
MilerLifecycle.report('pickup-complete', pivot);
// ── The Picked half of the lifecycle trace ──
//
// Unconditional, and it prints the response body. `_send` logs only
// failures, so a pivot that succeeded and wrote the *wrong* state —
// which is the entire production question — left no record at all, and
// every investigation of it started by adding this line by hand.
//
// Read with the `[TRACE][START-RIDE]` line in `releaseForDelivery`:
// together they answer "did the app release at Picked?" from one log,
// with no console access and no packet capture. See MilerLifecycle.
debugPrint(
'[TRACE][PICKED] booking=$id '
'POST /miler/bookings/$id/pickup-complete '
'-> ${picked.status} booking="${pivot.bookingStatus.name}" '
'consignment="${pivot.consignmentState.name}" '
'id="${pivot.consignmentId}" next="${pivot.nextAction}" '
'compatibilityMode=${pivot.isCompatibilityMode} '
'startDeliveryCalledHere=false '
'raw=${picked.raw}',
);
if (pivot.isCompatibilityMode) {
ApiConfig.logGap(
'pickup-complete',
@@ -301,11 +372,23 @@ class UpdatePickupProvider {
);
case 'skipped':
// Was a fabricated success — `okEnvelope('skip not supported by
// backend')` — so the rider watched a stop move to "skipped" while
// nothing left the phone and the hub never learned.
// ── The booking skip, not the consignment skip ──
//
// It was a fabricated success once — `okEnvelope('skip not supported
// by backend')` — so the rider watched a stop move to "skipped" while
// nothing left the phone. The fix that replaced it posted to
// `POST /miler/consignments/:id/skip` with a **booking** id, because
// that was the only skip route on the contract. Two different
// sequences: the call either 404'd or, worse, skipped whichever
// consignment happened to carry that number.
//
// Every caller of this branch is pre-collection — the skip sheet opens
// from a pickup card — and the pre-pickup skip now has its own route,
// which keeps the booking assigned and resumable. A skip *after*
// collection never comes through here: it goes through
// `closeDelivery`, which resolves the real consignment id first.
return envelope(
await MilerApi.skipConsignment(
await MilerApi.skipBooking(
id,
reason: notes.isEmpty ? 'Skipped by rider' : notes,
lat: lat,