// Shared mock booking data for development/testing List> 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> 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> 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> 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 = >[]; 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> 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 = { 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++) { ...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 _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 { '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', }; }