Files
doormile_milderapp/test/fixtures/demo_stops.dart
2026-08-11 13:16:33 +05:30

843 lines
25 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shared mock booking data for development/testing
List<Map<String, dynamic>> getMockQueues() {
final now = DateTime.now();
return [
{
'orderid': 'MOCK-Q-1001',
'pickupid': 'del-mock-q-1001',
'orderheaderid': '10001',
'orderstatus': 'accepted',
'pickupcustomer': 'Priya Sharma',
'pickupaddress': '45, Lake View Apartments, Race Course',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9558',
'pickuplat': '11.0243',
'pickuplong': '76.9652',
'kms': '2.1',
'Quantity': '3',
'notes': 'Documents parcel - handover at door',
'expected_pickup_time': now
.add(const Duration(minutes: 15))
.toIso8601String(),
'riderslat': '11.0180',
'riderslon': '76.9580',
},
{
'orderid': 'MOCK-Q-1002',
'pickupid': 'del-mock-q-1002',
'orderheaderid': '10002',
'orderstatus': '',
'pickupcustomer': 'Arun Venkatesh',
'pickupaddress': '23, Teachers Colony, Saibaba Colony',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9678',
'pickuplat': '11.0312',
'pickuplong': '76.9721',
'kms': '1.5',
'Quantity': '8',
'notes': 'Clothes & accessories - packed in bags',
'expected_pickup_time': now
.add(const Duration(minutes: 30))
.toIso8601String(),
'riderslat': '11.0180',
'riderslon': '76.9580',
},
{
'orderid': 'MOCK-Q-1003',
'pickupid': 'del-mock-q-1003',
'orderheaderid': '10003',
'orderstatus': '',
'pickupcustomer': 'Meena Krishnan',
'pickupaddress': '5/2, North Avenue, Vadavalli',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9902',
'pickuplat': '11.0358',
'pickuplong': '76.9985',
'kms': '3.2',
'Quantity': '12',
'notes': 'Electronics item - handle with care',
'expected_pickup_time': now
.add(const Duration(minutes: 45))
.toIso8601String(),
'riderslat': '11.0180',
'riderslon': '76.9580',
},
{
'orderid': 'MOCK-Q-1004',
'pickupid': 'del-mock-q-1004',
'orderheaderid': '10004',
'orderstatus': 'accepted',
'pickupcustomer': 'Vikram Singh',
'pickupaddress': '88, Royal Nagar, Singanallur',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9428',
'pickuplat': '11.0198',
'pickuplong': '76.9512',
'kms': '1.8',
'Quantity': '5',
'notes': 'Gift items - fragile, handle gently',
'expected_pickup_time': now
.add(const Duration(minutes: 20))
.toIso8601String(),
'riderslat': '11.0180',
'riderslon': '76.9580',
},
{
'orderid': 'MOCK-Q-1005',
'pickupid': 'del-mock-q-1005',
'orderheaderid': '10005',
'orderstatus': '',
'pickupcustomer': 'Divya Menon',
'pickupaddress': '120, West Street, Thudiyalur',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9889',
'pickuplat': '11.0318',
'pickuplong': '76.9965',
'kms': '2.8',
'Quantity': '15',
'notes': 'Books and stationery - secure packaging',
'expected_pickup_time': now
.add(const Duration(minutes: 60))
.toIso8601String(),
'riderslat': '11.0180',
'riderslon': '76.9580',
},
];
}
List<Map<String, dynamic>> getMockBookingPageItems() {
final now = DateTime.now();
return [
{
'orderid': 'MOCK-B-3001',
'pickupid': '3001',
'orderheaderid': '103001',
'orderstatus': 'accepted',
'step': 1,
'pickupcustomer': 'Lakshmi Narayanan',
'pickupaddress': '67, East Gate, Uppilipalayam',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9500',
'pickuplat': '11.0168',
'pickuplong': '76.9558',
'droplat': '11.0168',
'droplon': '76.9558',
'riderslat': '11.0180',
'riderslon': '76.9580',
'kms': '2.4',
'Quantity': '6',
'notes': 'Ready for pickup - ring the bell',
'pickupcontactno': '9876543210',
'expected_pickup_time': now
.add(const Duration(minutes: 12))
.toIso8601String(),
},
{
'orderid': 'MOCK-B-3002',
'pickupid': '3002',
'orderheaderid': '103002',
'orderstatus': 'pending',
'step': 2,
'pickupcustomer': 'Rajesh Bhat',
'pickupaddress': '4, Sunshine Apartments, R.S. Puram',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9419',
'pickuplat': '11.0347',
'pickuplong': '76.9468',
'droplat': '11.0347',
'droplon': '76.9468',
'riderslat': '11.0180',
'riderslon': '76.9580',
'kms': '3.1',
'Quantity': '4',
'notes': 'Parcel - handle with care',
'pickupcontactno': '9876543211',
'expected_pickup_time': now
.add(const Duration(minutes: 24))
.toIso8601String(),
},
{
'orderid': 'MOCK-B-3003',
'pickupid': '3003',
'orderheaderid': '103003',
'orderstatus': 'pending',
'step': 3,
'pickupcustomer': 'Neha Agarwal',
'pickupaddress': '22, Lake View Road, Koundampalayam',
'tenantname': 'Miler Logistics',
'pickuplon': '77.0397',
'pickuplat': '11.0254',
'pickuplong': '77.0102',
'droplat': '11.0254',
'droplon': '77.0102',
'riderslat': '11.0180',
'riderslon': '76.9580',
'kms': '4.6',
'Quantity': '2',
'notes': 'Urgent pickup - handle with care',
'pickupcontactno': '9876543212',
'expected_pickup_time': now
.add(const Duration(minutes: 38))
.toIso8601String(),
},
{
'orderid': 'MOCK-B-3004',
'pickupid': '3004',
'orderheaderid': '103004',
'orderstatus': 'skipped',
'step': 4,
'pickupcustomer': 'Anjali Nair',
'pickupaddress': '10, New Colony, Saravanampatti',
'tenantname': 'Miler Logistics',
'pickuplon': '76.9600',
'pickuplat': '11.0198',
'pickuplong': '76.9512',
'droplat': '11.0198',
'droplon': '76.9512',
'riderslat': '11.0180',
'riderslon': '76.9580',
'kms': '1.8',
'Quantity': '5',
'notes': 'Previously skipped - resume when ready',
'pickupcontactno': '9876543213',
'expected_pickup_time': now
.add(const Duration(minutes: 45))
.toIso8601String(),
},
];
}
List<Map<String, dynamic>> getMockPickups() {
final now = DateTime.now();
return [
{
'orderid': 'MOCK-D-2001',
'pickupid': 'del-mock-d-2001',
'orderstatus': 'picked',
'step': 1,
'pickupcustomer': 'Sarah Johnson',
'pickupaddress': '42 Maple Street, Apt 3B, New York, NY 10001',
'tenantname': 'Miler Logistics',
'pickuplong': -74.0060,
'pickuplat': 40.7580,
'pickuplon': -73.9855,
'kms': '3.2',
'notes': 'Ring doorbell, leave at door',
'expected_pickup_time': now
.add(const Duration(minutes: 25))
.toIso8601String(),
},
{
'orderid': 'MOCK-D-2002',
'pickupid': 'del-mock-d-2002',
'orderstatus': 'picked',
'step': 2,
'pickupcustomer': 'Michael Chen',
'pickupaddress': '158 Broadway, Floor 5, New York, NY 10013',
'tenantname': 'Miler Logistics',
'pickuplong': -74.0020,
'pickuplat': 40.7580,
'pickuplon': -73.9855,
'kms': '1.8',
'notes': 'Call upon arrival, building requires access code #4521',
'expected_pickup_time': now
.add(const Duration(minutes: 40))
.toIso8601String(),
},
{
'orderid': 'MOCK-D-2003',
'pickupid': 'del-mock-d-2003',
'orderstatus': 'picked',
'step': 3,
'pickupcustomer': 'Emily Rodriguez',
'pickupaddress': '275 7th Avenue, Suite 1200, New York, NY 10011',
'tenantname': 'Miler Logistics',
'pickuplong': -73.9950,
'pickuplat': 40.7580,
'pickuplon': -73.9855,
'kms': '2.5',
'notes': 'Pickup to reception desk',
'expected_pickup_time': now
.add(const Duration(minutes: 55))
.toIso8601String(),
},
{
'orderid': 'MOCK-D-2004',
'pickupid': 'del-mock-d-2004',
'orderstatus': 'picked',
'step': 4,
'pickupcustomer': 'James Wilson',
'pickupaddress': '88 University Place, Ground Floor, New York, NY 10003',
'tenantname': 'Miler Logistics',
'pickuplong': -73.9920,
'pickuplat': 40.7580,
'pickuplon': -73.9855,
'kms': '1.2',
'notes': '',
'expected_pickup_time': now
.add(const Duration(minutes: 70))
.toIso8601String(),
},
{
'orderid': 'MOCK-D-2005',
'pickupid': 'del-mock-d-2005',
'orderstatus': 'skipped',
'step': 5,
'pickupcustomer': 'Anna Kim',
'pickupaddress': '301 W 53rd Street, Apt 12A, New York, NY 10019',
'tenantname': 'Miler Logistics',
'pickuplong': -73.9850,
'pickuplat': 40.7580,
'pickuplon': -73.9855,
'kms': '4.1',
'notes': 'Cash payment expected',
'expected_pickup_time': now
.add(const Duration(minutes: 90))
.toIso8601String(),
},
];
}
// ─────────────────────────────────────────────────────────────────────────────
// DEMO TRIPS — three slot windows, every stop type, every decision state.
//
// ── This is a TEST FIXTURE. It does not ship. ──
//
// It used to live in `lib/data/mock_bookings.dart` and fill Home, Bookings and
// the handoff screen whenever the live backend answered with an empty list —
// gated on `kDebugMode`, which sounds safe and is not: the person running the
// debug build is the person deciding whether the feature works, and a screen
// full of convincing invented stops is exactly how a dead endpoint gets signed
// off. The app now shows what the hub actually said, including nothing.
//
// What survives is its second, legitimate job: a rich, deterministic route to
// test the pure `Trip` grouping logic against. Three trips covering every stop
// type in every state is a better fixture than anything a test would build by
// hand, so it moved here rather than being deleted.
// ─────────────────────────────────────────────────────────────────────────────
/// Three trips covering every stop type in every state the rider can see.
///
/// Trip 1 (09:00–12:00) — part-worked. A completed pickup AND a completed
/// delivery, a combined stop in progress, a skipped
/// delivery, and two still awaiting a decision. This
/// is the tab to look at for the locked rows, the
/// amber skip state and the completion ring.
/// Trip 2 (12:00–15:00) — delivery-heavy, all undecided. Multi-parcel drops,
/// a prepaid run, and two combined stops.
/// Trip 3 (15:00–18:00) — combined-heavy, all undecided, and the longest,
/// so select-all and multi-select are worth using.
///
/// **Different data per miler.** This used to return one hard-coded list, so
/// every account — Ramesh, the next tester, the demo rider — opened Home to the
/// exact same six customers, and the same `DM-21xx` order ids. Identical ids
/// across accounts is the worse half: accept/reject is persisted locally by
/// order id, so decisions made while signed in as one miler came back as
/// already-accepted stops for the next one.
///
/// So the *skeleton* is fixed and the *identity* is per rider. Which trip a stop
/// is in, its step, type, status and time window never change — that is what
/// guarantees every UI branch still has something to render, for every miler.
/// The customer, address, coordinates, parcel counts, cash and order ids are
/// drawn from [riderId] / [riderName], deterministically: the same miler gets
/// the same route every launch, two milers never get the same one.
///
/// Called with no rider (signed out) it still yields a valid route — seed 0.
List<Map<String, dynamic>> getDemoTripStops({String? riderName, int? riderId}) {
final now = DateTime.now();
DateTime at(int hour, int minute) =>
DateTime(now.year, now.month, now.day, hour, minute);
final seed = _demoSeed(riderName, riderId);
final rng = _DemoRandom(seed);
// Three digits stamped into every order id, so one miler's demo stops can
// never be mistaken — by the accepted/rejected store or by a human reading
// logs — for another's.
final tag = 100 + (seed % 900);
// Where this miler's route runs. Stepping through the shared pool by a stride
// coprime with its length visits 18 distinct places, and a rider-specific
// start + stride means two milers overlap in few of them.
final start = seed % _demoPlaces.length;
final stride = const [1, 5, 7, 11, 13][seed % 5];
final stops = <Map<String, dynamic>>[];
for (var i = 0; i < _demoSkeleton.length; i++) {
final slot = _demoSkeleton[i];
final place = _demoPlaces[(start + i * stride) % _demoPlaces.length];
// Loads vary per miler, but stay in the range each stop type implies: a
// delivery drops what it carries, a pickup collects, a combined does both.
final int? deliveryQty = slot.type == 'pickup'
? null
: rng.between(1, slot.type == 'delivery' ? 4 : 3);
final int? pickupQty = slot.type == 'delivery'
? null
: rng.between(1, slot.type == 'pickup' ? 8 : 5);
stops.add(
_demoStop(
id: 'DM-$tag${slot.code}',
step: slot.step,
type: slot.type,
status: slot.status,
customer: place.customer,
address: place.address,
lat: place.lat,
lng: place.lng,
qty: pickupQty ?? deliveryQty ?? 1,
deliveryQty: deliveryQty,
pickupQty: pickupQty,
// Round sums to the nearest ten — a COD figure of ₹1247 reads as a bug.
cash: slot.cod ? (rng.between(15, 140) * 10).toDouble() : null,
notes: slot.notes,
due: at(slot.hour, slot.minute),
),
);
}
return stops;
}
/// The Bookings tab's demo route — the same stops Home hands out, in the states
/// a rider who has *already accepted them* would see.
///
/// Bookings was empty on the live backend for two separate reasons, and fixing
/// only one would have left it empty anyway:
///
/// 1. Its mock fallback was gated on `!ApiConfig.useNewApi`, and the new API
/// is the default — the same gate Home and Earnings had.
/// 2. Even with stops injected, the tab drops anything still pending: it
/// shows work the rider has *taken on*, and `getDemoTripStops` returns
/// mostly `status: 'new'`, which that filter removes. So demo stops have
/// to arrive already accepted, or they vanish silently.
///
/// This takes the miler's own route and rewrites the first twelve stops into a
/// believable working day, so the tab has something in every state it can
/// render: a couple finished (green rail), one active (the card that blocks the
/// others), one skipped (amber, awaiting a return), and the rest queued.
List<Map<String, dynamic>> getDemoBookings({String? riderName, int? riderId}) {
final stops = getDemoTripStops(riderName: riderName, riderId: riderId);
if (stops.isEmpty) return stops;
// Trips 1 and 2 — twelve stops is a full screen's worth of scrolling without
// pretending a rider has a forty-stop day.
final slice = stops.take(12).toList();
// Positions chosen so the progress bar shows every colour it has, and the
// active stop is early enough that queued stops sit visibly below it.
const statuses = <int, String>{
0: 'picked', // done
1: 'delivered', // done
2: 'active', // the one he is on
3: 'skipped', // come back to it
};
return [
for (var i = 0; i < slice.length; i++)
<String, dynamic>{
...slice[i],
// Everything else is accepted: taken on, not yet started. That is the
// state the Bookings filter is looking for.
'orderstatus': statuses[i] ?? 'accepted',
},
];
}
/// Stable per-miler seed. FNV-1a over the identity, so neighbouring rider ids
/// (9998, 9999) land far apart instead of drawing near-identical routes.
int _demoSeed(String? riderName, int? riderId) {
final key = (riderId != null && riderId > 0)
? 'id:$riderId'
: 'name:${(riderName ?? '').trim().toLowerCase()}';
if (key == 'name:') return 0;
var h = 2166136261;
for (final c in key.codeUnits) {
h ^= c;
h = (h * 16777619) & 0xFFFFFFFF;
}
return h & 0x7FFFFFFF;
}
/// Deterministic per-miler number source. Not `dart:math`'s Random — this must
/// give the same route on every launch for the same rider, and be seedable from
/// an id rather than the clock.
class _DemoRandom {
int _state;
_DemoRandom(int seed) : _state = (seed | 1) & 0x7FFFFFFF;
int between(int min, int max) {
_state = (_state * 1103515245 + 12345) & 0x7FFFFFFF;
return min + ((_state >> 8) % (max - min + 1));
}
}
/// A place a demo stop can happen at. The pool is shared; which slice of it a
/// miler gets is decided by their seed.
class _DemoPlace {
final String customer;
final String address;
final double lat;
final double lng;
const _DemoPlace(this.customer, this.address, this.lat, this.lng);
}
/// 36 addresses across Coimbatore — twice what one miler's day uses, so two
/// milers' routes look properly different rather than reshuffled.
const List<_DemoPlace> _demoPlaces = [
_DemoPlace(
'Priya Sharma',
'45, Lake View Apartments, Race Course, Coimbatore',
11.0243,
76.9558,
),
_DemoPlace(
'Arun Venkatesh',
'23, Teachers Colony, Saibaba Colony, Coimbatore',
11.0312,
76.9678,
),
_DemoPlace(
'Meena Krishnan',
'5/2, North Avenue, Vadavalli, Coimbatore',
11.0358,
76.9902,
),
_DemoPlace(
'Ravi Subramanian',
'12A, Puliakulam Road, Coimbatore',
11.0021,
76.9721,
),
_DemoPlace(
'Sri Balaji Stores',
'19, Thadagam Road, Venkitapuram, Coimbatore',
11.0289,
76.9312,
),
_DemoPlace(
'Sundar Textiles',
'78, Cross Cut Road, Gandhipuram, Coimbatore',
11.0168,
76.9558,
),
_DemoPlace(
'Kavitha Ramesh',
'12, Bharathi Park Road, Coimbatore',
11.0089,
76.9712,
),
_DemoPlace(
'Hari Prasad',
'7, DB Road, RS Puram, Coimbatore',
11.0043,
76.9498,
),
_DemoPlace(
'Lakshmi Stores',
'61, Trichy Road, Ramanathapuram, Coimbatore',
10.9931,
76.9887,
),
_DemoPlace(
'Anand Electronics',
'3, Oppanakara Street, Town Hall, Coimbatore',
10.9975,
76.9601,
),
_DemoPlace(
'Nandhini Bakery',
'54, 100 Feet Road, Gandhipuram, Coimbatore',
11.0198,
76.9673,
),
_DemoPlace(
'Divya Fashions',
'9, Nava India Road, Peelamedu, Coimbatore',
11.0271,
77.0021,
),
_DemoPlace(
'Ganesh Traders',
'134, Avinashi Road, Hope College, Coimbatore',
11.0301,
77.0142,
),
_DemoPlace(
'Rekha Nair',
'27, Kalapatti Main Road, Coimbatore',
11.0521,
77.0312,
),
_DemoPlace(
'Bright Pharmacy',
'15, Mettupalayam Road, Thudiyalur, Coimbatore',
11.0712,
76.9401,
),
_DemoPlace(
'Selvam Hardware',
'88, Sathy Road, Ganapathy, Coimbatore',
11.0432,
76.9821,
),
_DemoPlace(
'Vetri Apartments',
'4B, Saravanampatti, Coimbatore',
11.0798,
77.0089,
),
_DemoPlace(
'Karthik Rajan',
'31, Vilankurichi Road, Coimbatore',
11.0644,
77.0201,
),
_DemoPlace(
'Muthu Silks',
'22, Raja Street, Town Hall, Coimbatore',
10.9968,
76.9585,
),
_DemoPlace(
'Aishwarya Gopal',
'8, Kamaraj Road, Sowripalayam, Coimbatore',
10.9924,
76.9974,
),
_DemoPlace(
'Velan Provisions',
'41, Ukkadam Main Road, Coimbatore',
10.9861,
76.9563,
),
_DemoPlace(
'Deepa Iyer',
'16, TV Samy Road, RS Puram, Coimbatore',
11.0071,
76.9531,
),
_DemoPlace(
'Kongu Steel Mart',
'102, Mettupalayam Road, Kavundampalayam, Coimbatore',
11.0489,
76.9382,
),
_DemoPlace(
'Sathya Agencies',
'7, Perur Main Road, Kuniamuthur, Coimbatore',
10.9639,
76.9302,
),
_DemoPlace(
'Ramya Sundaram',
'63, Perur Main Road, Kuniamuthur, Marudhamalai Road, Vadavalli, Coimbatore - 634783 ',
11.0301,
76.9151,
),
_DemoPlace(
'Green Leaf Grocers',
'29, Kovaipudur Main Road, Coimbatore',
10.9291,
76.9128,
),
_DemoPlace(
'Prakash Auto Parts',
'55, Sungam Bypass Road, Coimbatore',
10.9832,
76.9752,
),
_DemoPlace(
'Janaki Ammal',
'3, Vivekananda Road, Ram Nagar, Coimbatore',
11.0092,
76.9663,
),
_DemoPlace(
'Coimbatore Book House',
'19, Bashyakarlu Road, RS Puram, Coimbatore',
11.0038,
76.9457,
),
_DemoPlace(
'Tharun Sports',
'74, Tatabad, Gandhipuram, Coimbatore',
11.0212,
76.9631,
),
_DemoPlace(
'Malathi Nursing Home',
'12, Trichy Road, Singanallur, Coimbatore',
10.9948,
77.0298,
),
_DemoPlace(
'Sowmya Devi',
'47, Kurichi Main Road, Coimbatore',
10.9418,
76.9601,
),
_DemoPlace(
'Ashok Timbers',
'91, Palakkad Main Road, Madukkarai, Coimbatore',
10.9061,
76.9327,
),
_DemoPlace(
'Nithya Sweets',
'5, Cheran Managar, Kalapatti, Coimbatore',
11.0562,
77.0221,
),
_DemoPlace(
'Vasanth Motors',
'66, Karumathampatti Road, Neelambur, Coimbatore',
11.0402,
77.1012,
),
_DemoPlace(
'Elango Krishnamoorthy',
'18, Chinnavedampatti Road, Coimbatore',
11.0601,
76.9902,
),
];
/// One position on the day. Everything here is the same for every miler — this
/// is the state-coverage contract the demo data exists to provide.
class _DemoSlot {
/// Trailing digits of the order id: trip number + step. Kept stable so a
/// given miler's stop 3 of trip 2 is always `…2203`.
final String code;
final int step;
/// pickup | delivery | combined
final String type;
/// new | picked | delivered | arrived | skipped
final String status;
final int hour;
final int minute;
/// Whether this stop carries cash. The amount varies per miler; whether the
/// COD branch renders at all does not.
final bool cod;
final String? notes;
const _DemoSlot(
this.code,
this.step,
this.type,
this.hour,
this.minute, {
this.status = 'new',
this.cod = false,
this.notes,
});
}
const List<_DemoSlot> _demoSkeleton = [
// ── Trip 1 · 09:00 – 12:00 · part-worked ────────────────────────────────
_DemoSlot('2101', 1, 'pickup', 9, 20, status: 'picked'), // done
// A finished DELIVERY, not just a finished pickup.
_DemoSlot('2102', 2, 'delivery', 9, 55, status: 'delivered'),
// Active → the locked "In progress" row.
_DemoSlot('2103', 3, 'combined', 10, 30, status: 'arrived', cod: true),
_DemoSlot(
'2104',
4,
'delivery',
10,
50,
status: 'skipped',
notes: 'Customer asked to come back after 11am',
),
_DemoSlot('2105', 5, 'combined', 11, 15),
_DemoSlot(
'2106',
6,
'pickup',
11,
40,
notes: 'Bulk order — ask for the back entrance',
),
// ── Trip 2 · 12:00 – 15:00 · delivery-heavy, all undecided ──────────────
_DemoSlot('2201', 1, 'delivery', 12, 15),
_DemoSlot('2202', 2, 'delivery', 12, 40, notes: 'Buzz flat 3B'),
_DemoSlot('2203', 3, 'combined', 13, 5),
_DemoSlot('2204', 4, 'pickup', 13, 35, cod: true),
// Cash on the pickup leg of a combined stop.
_DemoSlot('2205', 5, 'combined', 14, 0, cod: true),
_DemoSlot('2206', 6, 'delivery', 14, 35),
// ── Trip 3 · 15:00 – 18:00 · combined-heavy, all undecided ──────────────
_DemoSlot('2301', 1, 'combined', 15, 20),
_DemoSlot('2302', 2, 'delivery', 15, 50),
_DemoSlot(
'2303',
3,
'combined',
16,
15,
cod: true,
notes: 'Cold chain box — do not leave in the sun',
),
_DemoSlot(
'2304',
4,
'pickup',
16,
45,
cod: true,
notes: 'Heavy items — bring the large box',
),
_DemoSlot('2305', 5, 'combined', 17, 15),
_DemoSlot('2306', 6, 'delivery', 17, 45),
];
/// Builds one demo stop in the legacy stop shape the UI reads.
Map<String, dynamic> _demoStop({
required String id,
required int step,
required String type,
required String customer,
required String address,
required double lat,
required double lng,
required DateTime due,
String status = 'new',
int qty = 1,
int? deliveryQty,
int? pickupQty,
double? cash,
String? notes,
}) {
return <String, dynamic>{
'orderid': id,
'pickupid': id.replaceAll('DM-', ''),
'orderheaderid': id.replaceAll('DM-', ''),
'pickuplocationid': '1',
'orderstatus': status,
'step': step,
'type': type,
'pickupcustomer': customer,
'tenantname': 'Doormile',
'pickupaddress': address,
'pickupcontactno': '9876543210',
'pickuplat': '$lat',
'pickuplon': '$lng',
'pickuplong': '$lng',
'Quantity': '$qty',
if (deliveryQty != null) 'deliveryqty': '$deliveryQty',
if (pickupQty != null) 'pickupqty': '$pickupQty',
if (cash != null) 'collectionamt': '$cash',
if (notes != null) 'notes': notes,
'weight': '${(qty * 1.4).toStringAsFixed(1)}',
'expected_pickup_time': due.toIso8601String(),
'otp': type == 'pickup' ? '' : '4821',
};
}