device infos
This commit is contained in:
@@ -71,7 +71,7 @@ Future<void> migrateLegacyStores() async {
|
||||
_kConsignmentIdsKeyBase,
|
||||
_kOutForDeliveryKeyBase,
|
||||
_kNotLoadedKeyBase,
|
||||
_kBagLabelsKeyBase,
|
||||
_kOrderLabelsKeyBase,
|
||||
]) {
|
||||
await _drainLegacy(base, scope);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ Future<void> clearScopedStores() async {
|
||||
_kConsignmentIdsKeyBase,
|
||||
_kOutForDeliveryKeyBase,
|
||||
_kNotLoadedKeyBase,
|
||||
_kBagLabelsKeyBase,
|
||||
_kOrderLabelsKeyBase,
|
||||
]) {
|
||||
await prefs.remove(scope.scoped(base));
|
||||
await prefs.remove(base);
|
||||
@@ -296,6 +296,26 @@ String kStopStartedKey(Object pickupId) => 'stop_started_$pickupId';
|
||||
/// Written by the stop map screen when it starts the pickup.
|
||||
String kStopArrivedKey(Object pickupId) => 'pickup_start_$pickupId';
|
||||
|
||||
/// Prefs key holding the kilometres the app measured for a stop as it closed.
|
||||
///
|
||||
/// ── The app measured this, posted it, and kept no copy ──
|
||||
///
|
||||
/// `PickupsController.updateToPicked` works out how far the rider came —
|
||||
/// `Geolocator.distanceBetween`, rider fix to pickup point — and sends it as
|
||||
/// `actualkms`. Then it dropped it. The number existed for the length of one
|
||||
/// HTTP request.
|
||||
///
|
||||
/// Which is why Activity's `km ridden` read an em dash on a day the rider had
|
||||
/// plainly ridden: [StopCompliance] looks for `actualkms` on the row, the row
|
||||
/// never carried one, and `GET /miler/bookings` does not reliably send the
|
||||
/// distance back. The app was the only thing that knew, and it forgot.
|
||||
///
|
||||
/// So it is written here as it is posted, and [addCompletedBookings] merges it
|
||||
/// onto the record it files — the same rescue it already performs for the two
|
||||
/// clocks beside it, for the same reason: the moment the fact is true is the
|
||||
/// moment to write it down.
|
||||
String kStopKmKey(Object pickupId) => 'stop_km_$pickupId';
|
||||
|
||||
/// Record a finished stop. [cancelled] separates "could not complete" from a
|
||||
/// clean pickup/delivery — Activity shows both, worded differently.
|
||||
///
|
||||
@@ -362,10 +382,18 @@ Future<void> addCompletedBookings(
|
||||
final arrived = prefs.getString(kStopArrivedKey(pickupId));
|
||||
if (started != null && started.isNotEmpty) copy['startedat'] = started;
|
||||
if (arrived != null && arrived.isNotEmpty) copy['arrivedat'] = arrived;
|
||||
// The distance the app measured as it closed this stop — see
|
||||
// [kStopKmKey]. A figure already on the row wins: that one came from the
|
||||
// hub, and this is the app's own measurement standing in for it.
|
||||
final km = prefs.getString(kStopKmKey(pickupId));
|
||||
if (km != null && km.isNotEmpty && (copy['actualkms'] ?? '') == '') {
|
||||
copy['actualkms'] = km;
|
||||
}
|
||||
// Cleared, or a stop worked twice in a day (a resumed skip) reports the
|
||||
// first attempt's clock against the second attempt's completion.
|
||||
await prefs.remove(kStopStartedKey(pickupId));
|
||||
await prefs.remove(kStopArrivedKey(pickupId));
|
||||
await prefs.remove(kStopKmKey(pickupId));
|
||||
}
|
||||
|
||||
// Stamped with the session that produced it, so a row read back later
|
||||
@@ -795,26 +823,36 @@ Future<void> addNotLoadedOrderIds(List<String> ids) async {
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// BAG IDENTITY — which bag an order travels in, for as long as it travels
|
||||
// THE COUNTER'S OWN LABEL — carried for as long as the order travels
|
||||
//
|
||||
// ── Why this is stored rather than recomputed ──
|
||||
//
|
||||
// A bag number is the order's position in the load it came off — Joe is Bag 1
|
||||
// of the five that left Vidhya Kitchen. Recomputing that later from whatever
|
||||
// list happens to be on screen is how identity breaks: deliver Joe, and a
|
||||
// position-derived Arun silently becomes Bag 1. The rider is then looking for a
|
||||
// bag labelled 1 that is in a customer's hallway.
|
||||
// A kitchen that prints labels prints them once, at the counter, and the label
|
||||
// is what the rider matches against the object in his hands for the rest of the
|
||||
// day. Recomputing anything from whatever list happens to be on screen is how
|
||||
// identity breaks — deliver one order and a position-derived label silently
|
||||
// moves to another, sending the rider looking for something that is already in
|
||||
// a customer's hallway.
|
||||
//
|
||||
// So the pairing is fixed once, at the counter, at the moment the manifest is
|
||||
// confirmed — and read back unchanged through delivery, skip, completion and a
|
||||
// day of intermittent signal. See [BagManifest], which derives it; this only
|
||||
// remembers what it derived.
|
||||
// So the pairing is fixed once, at the moment the manifest is confirmed, and
|
||||
// read back unchanged through delivery, skip, completion and a day of
|
||||
// intermittent signal. See [OrderManifest.labelFor], which reads it off the
|
||||
// payload; this only remembers what was read.
|
||||
//
|
||||
// ── It is empty on most routes now, and that is correct ──
|
||||
//
|
||||
// This used to hold `Bag 1 … Bag n`, manufactured from each order's position
|
||||
// when the payload printed nothing — so every route stored a set of references
|
||||
// no counter had ever issued. Only a label the kitchen actually printed is
|
||||
// stored now, which on a route that prints none means nothing is stored at all
|
||||
// and every screen simply shows no tag.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
const String _kBagLabelsKeyBase = 'order_bag_labels';
|
||||
const String _kOrderLabelsKeyBase = 'order_bag_labels';
|
||||
|
||||
/// Remembers which bag each order travels in. Merges, never replaces: a rider
|
||||
/// works two kitchens and the second load must not erase the first.
|
||||
Future<void> saveBagLabels(Map<String, String> byOrderId) async {
|
||||
/// Remembers the label a counter printed on each order. Merges, never
|
||||
/// replaces: a rider works two kitchens and the second load must not erase the
|
||||
/// first. Orders with no printed label contribute nothing.
|
||||
Future<void> saveOrderLabels(Map<String, String> byOrderId) async {
|
||||
final clean = {
|
||||
for (final e in byOrderId.entries)
|
||||
if (e.key.trim().isNotEmpty && e.value.trim().isNotEmpty)
|
||||
@@ -823,19 +861,20 @@ Future<void> saveBagLabels(Map<String, String> byOrderId) async {
|
||||
if (clean.isEmpty) return;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final merged = {...await getBagLabels(), ...clean};
|
||||
final merged = {...await getOrderLabels(), ...clean};
|
||||
// Stored as `id\u0000label` rows: SharedPreferences has no map type, and a
|
||||
// NUL separator cannot collide with an order id or a bag label.
|
||||
await prefs.setStringList(await _scopedKey(_kBagLabelsKeyBase), [
|
||||
// NUL separator cannot collide with an order id or a printed label.
|
||||
await prefs.setStringList(await _scopedKey(_kOrderLabelsKeyBase), [
|
||||
for (final e in merged.entries) '${e.key}\u0000${e.value}',
|
||||
]);
|
||||
}
|
||||
|
||||
/// The bag each order is in, as recorded at pickup. Empty before any load.
|
||||
Future<Map<String, String>> getBagLabels() async {
|
||||
/// The label each order carries, as recorded at pickup. Empty before any load,
|
||||
/// and empty all day on a route whose counters print nothing.
|
||||
Future<Map<String, String>> getOrderLabels() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final rows =
|
||||
prefs.getStringList(await _scopedKey(_kBagLabelsKeyBase)) ??
|
||||
prefs.getStringList(await _scopedKey(_kOrderLabelsKeyBase)) ??
|
||||
const <String>[];
|
||||
return {
|
||||
for (final row in rows)
|
||||
@@ -854,7 +893,7 @@ Future<void> clearServiceRunState() async {
|
||||
await prefs.remove(await _scopedKey(_kCollectedOrderIdsKeyBase));
|
||||
await prefs.remove(await _scopedKey(_kOutForDeliveryKeyBase));
|
||||
await prefs.remove(await _scopedKey(_kNotLoadedKeyBase));
|
||||
await prefs.remove(await _scopedKey(_kBagLabelsKeyBase));
|
||||
await prefs.remove(await _scopedKey(_kOrderLabelsKeyBase));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
@@ -933,16 +972,16 @@ Future<int> purgeDemoRecords() async {
|
||||
// Harmless on its own, but this store exists to be the one place that knows
|
||||
// which bag an order is in, and a stale entry is exactly the kind of thing
|
||||
// that is trusted later precisely because it is stored.
|
||||
final bags =
|
||||
prefs.getStringList(await _scopedKey(_kBagLabelsKeyBase)) ??
|
||||
final labels =
|
||||
prefs.getStringList(await _scopedKey(_kOrderLabelsKeyBase)) ??
|
||||
const <String>[];
|
||||
if (bags.isNotEmpty) {
|
||||
final kept = bags
|
||||
if (labels.isNotEmpty) {
|
||||
final kept = labels
|
||||
.where((row) => !_looksLikeDemoId(row.split('\u0000').first))
|
||||
.toList();
|
||||
if (kept.length != bags.length) {
|
||||
dropped += bags.length - kept.length;
|
||||
await prefs.setStringList(await _scopedKey(_kBagLabelsKeyBase), kept);
|
||||
if (kept.length != labels.length) {
|
||||
dropped += labels.length - kept.length;
|
||||
await prefs.setStringList(await _scopedKey(_kOrderLabelsKeyBase), kept);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user