miler map
This commit is contained in:
@@ -69,6 +69,7 @@ Future<void> migrateLegacyStores() async {
|
||||
_kArrivedOrderIdsKeyBase,
|
||||
_kCollectedOrderIdsKeyBase,
|
||||
_kConsignmentIdsKeyBase,
|
||||
_kPivotNextActionKeyBase,
|
||||
_kOutForDeliveryKeyBase,
|
||||
_kNotLoadedKeyBase,
|
||||
_kOrderLabelsKeyBase,
|
||||
@@ -115,6 +116,7 @@ Future<void> clearScopedStores() async {
|
||||
_kArrivedOrderIdsKeyBase,
|
||||
_kCollectedOrderIdsKeyBase,
|
||||
_kConsignmentIdsKeyBase,
|
||||
_kPivotNextActionKeyBase,
|
||||
_kOutForDeliveryKeyBase,
|
||||
_kNotLoadedKeyBase,
|
||||
_kOrderLabelsKeyBase,
|
||||
@@ -714,6 +716,89 @@ Future<void> forgetConsignmentIds(List<String> orderIds) async {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// THE PIVOT'S ROUTING ANSWER — which way this parcel went at pickup-complete
|
||||
//
|
||||
// `next_action` is the server naming the next leg: `start_delivery` for a
|
||||
// parcel this rider delivers himself, `inward_at_hub` for one that enters the
|
||||
// network. It is returned **once**, by the pivot, and it was being logged and
|
||||
// thrown away — the only trace of it was `lastPivotNextAction`, a single
|
||||
// in-memory string on a controller, which is not per order and does not
|
||||
// survive a rebuild.
|
||||
//
|
||||
// So a hub-routed parcel was indistinguishable from a hyperlocal one on the
|
||||
// very next poll, and `NextLegResolver` had nothing to fall back on when the
|
||||
// consignment read `Created` — the state that cannot decide anything by itself.
|
||||
//
|
||||
// ── This is continuity, not authority ──
|
||||
//
|
||||
// It is rung 4 of four, below every live server reading, deliberately: it says
|
||||
// what the server decided *at the pickup*, and if the server has since said
|
||||
// something different the server wins. See [NextLegResolver] for the full
|
||||
// precedence table.
|
||||
//
|
||||
// ── It should not have to exist ──
|
||||
//
|
||||
// The right home for this fact is the queue row. `GET /miler/bookings` does not
|
||||
// carry `next_action`, which is the logged backend gap this store works around.
|
||||
// When that field lands, `NextLegResolver` prefers it automatically and this
|
||||
// becomes a cache nothing reads.
|
||||
const String _kPivotNextActionKeyBase = 'pivot_next_action_by_order';
|
||||
|
||||
/// Records the `next_action` the pivot returned for [orderId].
|
||||
///
|
||||
/// Ignores an empty action rather than storing a blank: an absent instruction
|
||||
/// and a recorded "no instruction" are different facts, and only the first is
|
||||
/// true when the server said nothing.
|
||||
Future<void> rememberPivotNextAction(String orderId, String action) async {
|
||||
final id = orderId.trim();
|
||||
final value = action.trim().toLowerCase();
|
||||
if (id.isEmpty || value.isEmpty) return;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final map = await getPivotNextActions();
|
||||
map[id] = value;
|
||||
await prefs.setString(
|
||||
await _scopedKey(_kPivotNextActionKeyBase),
|
||||
jsonEncode(map),
|
||||
);
|
||||
debugPrint('[NEXTLEG] recorded $id → $value');
|
||||
}
|
||||
|
||||
/// Every pivot instruction this device recorded, by order id.
|
||||
Future<Map<String, String>> getPivotNextActions() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(await _scopedKey(_kPivotNextActionKeyBase));
|
||||
if (raw == null || raw.isEmpty) return <String, String>{};
|
||||
try {
|
||||
final decoded = jsonDecode(raw);
|
||||
if (decoded is! Map) return <String, String>{};
|
||||
return {
|
||||
for (final e in decoded.entries)
|
||||
e.key.toString(): (e.value?.toString() ?? '').toLowerCase(),
|
||||
}..removeWhere((_, v) => v.isEmpty);
|
||||
} catch (_) {
|
||||
return <String, String>{};
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops instructions for orders that are finished, so the map does not grow
|
||||
/// for the life of the install.
|
||||
Future<void> forgetPivotNextActions(List<String> orderIds) async {
|
||||
if (orderIds.isEmpty) return;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final map = await getPivotNextActions();
|
||||
var changed = false;
|
||||
for (final id in orderIds) {
|
||||
if (map.remove(id) != null) changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
await prefs.setString(
|
||||
await _scopedKey(_kPivotNextActionKeyBase),
|
||||
jsonEncode(map),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears ids once they are delivered — or once a short pick says they were
|
||||
/// never in the box to begin with. Without this the set grows for the life of
|
||||
/// the install and yesterday's run keeps today's cards off Home.
|
||||
|
||||
Reference in New Issue
Block a user