first commit

This commit is contained in:
2026-08-11 13:16:33 +05:30
parent 18531bf46b
commit c8350563d9
388 changed files with 69656 additions and 0 deletions

842
test/fixtures/demo_stops.dart vendored Normal file
View File

@@ -0,0 +1,842 @@
// 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',
};
}