/// ───────────────────────────────────────────────────────────────────────── /// WHO THE RIDER IS RINGING /// /// One number on a screen is not one fact. `Call` beside a kitchen's name and /// `Call` beside a customer's name are different promises, and a rider at a /// door who reaches the kitchen instead has lost the two minutes the control /// existed to save him. /// /// ── What the payload actually carries ── /// /// Verified against production 21 Aug 2026: `GET /miler/bookings` returns /// exactly **one** phone field, `customerphone`, which the adapter stores as /// `pickupcontactno` — a key named after the leg it was first read on rather /// than after whose number it is. There is no separate drop or receiver /// contact anywhere in the contract. /// /// So on today's data there is only one number to dial and it belongs to the /// booking's customer: the sender on a logistics collection, the subscriber on /// a meal delivery. Dialling it is correct on both legs. What was **not** /// correct is the app announcing it as "Call customer" while the rider is /// standing at a kitchen counter. /// /// This file therefore does two things: it prefers a genuine drop contact the /// moment the backend ships one — the precedence is written now so that day /// needs no archaeology — and it names who is being called, per leg, so the /// label can never drift from the number again. /// ───────────────────────────────────────────────────────────────────────── library; /// A number to dial, and who answers it. class StopContact { const StopContact(this.number, this.who); /// Empty when the stop carries no usable number. Callers omit the control /// rather than showing one that cannot dial. final String number; /// `the kitchen` / `the customer` — used to build the accessible label and /// any visible caption, so both are produced from one decision. final String who; bool get isEmpty => number.isEmpty; bool get isNotEmpty => number.isNotEmpty; /// `Call the customer`. String get action => 'Call $who'; /// Resolves the contact for the leg being worked. /// /// [delivery] is the leg, not the line: a milk run's collection at a kitchen /// is a pickup leg even though the line delivers to customers. Ask /// `MilkRun.navigatesToCustomer` for it rather than deriving it here — one /// answer to "which leg is this", used by the map, the sheet and this. static StopContact forLeg( Map stop, { required bool delivery, }) { String read(List keys) { for (final k in keys) { final v = (stop[k] ?? '').toString().trim(); if (v.isNotEmpty && v != 'null' && v != '0') return v; } return ''; } // Written for the contract that exists *and* the one that is coming: a // drop contact wins on a delivery leg the moment one is present, and until // then the single number the payload carries is used on both. const dropKeys = [ 'dropcontactno', 'DropContactNo', 'deliverycontactno', 'receiverphone', 'dropphone', ]; const pickupKeys = [ 'pickupcontactno', 'PickupContactNo', 'contactno', 'customerphone', ]; final number = delivery ? read([...dropKeys, ...pickupKeys]) : read([...pickupKeys, ...dropKeys]); return StopContact(number, delivery ? 'the customer' : 'the pickup'); } }