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

@@ -2,25 +2,35 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miler/data/accepted_store.dart';
import 'package:miler/data/bag_manifest.dart';
import 'package:miler/data/order_manifest.dart';
import 'package:miler/data/service_profile.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// THE DELIVERY QUEUE, AND THE BAG THAT SURVIVES IT
/// THE DELIVERY QUEUE, AND THE LABEL THAT SURVIVES IT
///
/// Two promises are made at a kitchen counter and both have to hold all the way
/// to a doorstep:
///
/// 1. **Five orders picked is five deliveries** — no order goes missing in the
/// hand-off, and nothing appears that was not collected.
/// 2. **Joe is Bag 1 and stays Bag 1** — even after Joe is delivered and the
/// list underneath him shrinks.
/// 2. **A label recorded at the counter is the label at the door** — even
/// after the first order is delivered and the list underneath it shrinks,
/// and even after a second counter loads on top of it.
///
/// The second one is the subtle one, and it is why bag identity is *stored* at
/// pickup rather than recomputed from whatever is on screen. A position-derived
/// bag number silently renames every remaining order the moment one leaves the
/// list, which sends the rider looking for a bag that is already in somebody's
/// The second is the subtle one, and it is why the label is *stored* at pickup
/// rather than recomputed from whatever is on screen. Anything derived from a
/// position silently renames every remaining order the moment one leaves the
/// list, which sends the rider looking for something already in somebody's
/// hallway.
///
/// ── The derived numbering is gone, and these tests changed with it ──
///
/// They used to assert `Bag 1 … Bag 5`, manufactured from each order's position
/// when the payload printed nothing — the app numbering its own list and
/// presenting the result as a physical fact. Only a label a counter actually
/// printed is stored now, so the fixtures carry real codes and a counter that
/// prints nothing stores nothing. The store's contract is unchanged and is
/// still what is pinned here.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -28,47 +38,50 @@ void main() {
setUp(() => SharedPreferences.setMockInitialValues({}));
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
Map<String, dynamic> order(String id, String customer) => {
/// [printed] is what the counter stuck on the order. Omitted, the counter
/// printed nothing — which is the common route, and stores nothing.
Map<String, dynamic> order(String id, String customer, {String? printed}) => {
'orderid': id,
'pickupcustomer': customer,
'kitchenname': 'Vidhya Kitchen',
'kitchenid': 'K1',
'quantity': 3,
if (printed != null) 'baglabel': printed,
};
final five = [
order('1', 'Joe'),
order('2', 'Arun'),
order('3', 'Priya'),
order('4', 'Kumar'),
order('5', 'Ravi'),
order('1', 'Joe', printed: 'DG-1001'),
order('2', 'Arun', printed: 'DG-1002'),
order('3', 'Priya', printed: 'DG-1003'),
order('4', 'Kumar', printed: 'DG-1004'),
order('5', 'Ravi', printed: 'DG-1005'),
];
group('the hand-off', () {
test(
'five picked orders become five deliveries, each with its bag',
'five picked orders become five deliveries, each with its label',
() async {
ServiceProfile.setActive(ServiceProfile.meals);
// What the pickup confirmation writes when the slide lands.
final manifest = BagManifest.forGroup(five);
final manifest = OrderManifest.forGroup(five);
await addCollectedOrderIds([for (final l in manifest) l.orderId]);
await saveBagLabels({for (final l in manifest) l.orderId: l.bag});
await saveOrderLabels({for (final l in manifest) l.orderId: l.label});
final carrying = await getCollectedOrderIds();
final bags = await getBagLabels();
final labels = await getOrderLabels();
expect(carrying.length, 5, reason: 'five picked is five to deliver');
expect(bags.length, 5, reason: 'and five bags, one each');
expect(bags['1'], 'Bag 1');
expect(bags['5'], 'Bag 5');
// No aggregate anywhere: the count of deliveries and the count of bags
// are the same set, not two numbers that can drift.
expect(bags.keys.toSet(), carrying);
expect(labels.length, 5, reason: 'and five labels, one each');
expect(labels['1'], 'DG-1001');
expect(labels['5'], 'DG-1005');
// No aggregate anywhere: the count of deliveries and the count of
// labels are the same set, not two numbers that can drift.
expect(labels.keys.toSet(), carrying);
},
);
test('a bag the counter could not supply never becomes a delivery', () async {
test('an order the counter could not supply never becomes a delivery', () async {
ServiceProfile.setActive(ServiceProfile.meals);
// Four handed over, one short — the shortfall path the pickup sheet owns.
@@ -76,8 +89,8 @@ void main() {
await addCollectedOrderIds([
for (final o in loaded) o['orderid'] as String,
]);
await saveBagLabels({
for (final l in BagManifest.forGroup(loaded)) l.orderId: l.bag,
await saveOrderLabels({
for (final l in OrderManifest.forGroup(loaded)) l.orderId: l.label,
});
await addNotLoadedOrderIds(['3']);
@@ -86,16 +99,16 @@ void main() {
// A short pick is its own record. It is not a skip: nobody was visited
// and the customer did nothing.
expect(await getSkippedOrderIds(), isEmpty);
// And it is not carrying a bag, because it was never in the box.
expect((await getBagLabels()).containsKey('3'), isFalse);
// And it carries no label, because it was never handed over.
expect((await getOrderLabels()).containsKey('3'), isFalse);
});
});
group('bag identity outlives the queue', () {
test('delivering the first does not renumber the rest', () async {
group('label identity outlives the queue', () {
test('delivering the first does not disturb the rest', () async {
ServiceProfile.setActive(ServiceProfile.meals);
await saveBagLabels({
for (final l in BagManifest.forGroup(five)) l.orderId: l.bag,
await saveOrderLabels({
for (final l in OrderManifest.forGroup(five)) l.orderId: l.label,
});
// Joe is delivered and leaves the carrying set.
@@ -104,38 +117,42 @@ void main() {
]);
await removeCollectedOrderIds(['1']);
final bags = await getBagLabels();
final labels = await getOrderLabels();
expect(await getCollectedOrderIds(), {'2', '3', '4', '5'});
// Arun does *not* become Bag 1 because the list in front of him shrank.
expect(bags['2'], 'Bag 2');
expect(bags['3'], 'Bag 3');
expect(bags['5'], 'Bag 5');
// Arun keeps the code the counter stuck on his order. Nothing is read
// off a position, so a list that shrinks under him changes nothing.
expect(labels['2'], 'DG-1002');
expect(labels['3'], 'DG-1003');
expect(labels['5'], 'DG-1005');
});
test('a second kitchen adds to the map rather than replacing it', () async {
ServiceProfile.setActive(ServiceProfile.meals);
await saveBagLabels({
for (final l in BagManifest.forGroup(five.take(2).toList()))
l.orderId: l.bag,
await saveOrderLabels({
for (final l in OrderManifest.forGroup(five.take(2).toList()))
l.orderId: l.label,
});
// A different counter, its own Bag 1 and Bag 2.
final second = [order('9', 'Meena'), order('10', 'Sai')];
await saveBagLabels({
for (final l in BagManifest.forGroup(second)) l.orderId: l.bag,
// A different counter, printing its own codes.
final second = [
order('9', 'Meena', printed: 'AM-2001'),
order('10', 'Sai', printed: 'AM-2002'),
];
await saveOrderLabels({
for (final l in OrderManifest.forGroup(second)) l.orderId: l.label,
});
final bags = await getBagLabels();
expect(bags.length, 4, reason: 'the first load must survive the second');
expect(bags['1'], 'Bag 1');
expect(bags['9'], 'Bag 1', reason: 'each counter counts its own shelf');
expect(bags['10'], 'Bag 2');
final labels = await getOrderLabels();
expect(labels.length, 4, reason: 'the first load must survive the second');
expect(labels['1'], 'DG-1001');
expect(labels['9'], 'AM-2001');
expect(labels['10'], 'AM-2002');
});
test('a skipped delivery keeps its bag so it can be resumed', () async {
test('a skipped delivery keeps its label so it can be resumed', () async {
ServiceProfile.setActive(ServiceProfile.meals);
await saveBagLabels({
for (final l in BagManifest.forGroup(five)) l.orderId: l.bag,
await saveOrderLabels({
for (final l in OrderManifest.forGroup(five)) l.orderId: l.label,
});
await addSkippedBooking(five[2], reason: 'Customer unavailable');
@@ -149,13 +166,13 @@ void main() {
);
// He is still carrying Priya's lunch — the bag identity is exactly what
// he needs when he comes back to it.
expect((await getBagLabels())['3'], 'Bag 3');
expect((await getOrderLabels())['3'], 'DG-1003');
});
test("a day's run is wiped so bags never leak into tomorrow", () async {
await saveBagLabels({'1': 'Bag 1'});
await saveOrderLabels({'1': 'Bag 1'});
await clearServiceRunState();
expect(await getBagLabels(), isEmpty);
expect(await getOrderLabels(), isEmpty);
});
});
@@ -164,10 +181,10 @@ void main() {
// bag map is keyed by order id, so it is swept by the same rule — a stored
// pairing is trusted later *because* it is stored, and a stale one is worse
// than none.
await saveBagLabels({'MOCK-M-1001': 'Bag 1', '77': 'Bag 2'});
await saveOrderLabels({'MOCK-M-1001': 'Bag 1', '77': 'Bag 2'});
await purgeDemoRecords();
final bags = await getBagLabels();
final bags = await getOrderLabels();
expect(bags.containsKey('MOCK-M-1001'), isFalse);
expect(bags['77'], 'Bag 2', reason: 'real work is untouched');
});