import 'package:miler/data/milk_run.dart'; import 'package:miler/views/Dashboard/pickups/stop_type.dart'; /// ───────────────────────────────────────────────────────────────────────── /// ONE PICKUP ORDER = ONE BAG /// /// The rule the whole rider workflow rests on, in one file so it cannot be /// stated two different ways on two screens. /// /// If a kitchen has five pickup orders, the rider is handed **five bags** — one /// per order — and ends up with **five deliveries**, each carrying the bag it /// arrived in: /// /// ``` /// Order 1 → Bag 1 → Joe /// Order 2 → Bag 2 → Arun /// Order 3 → Bag 3 → Priya /// ``` /// /// ── Why this is a file and not a `+ 1` at each call site ── /// /// Every screen in the pickup half of the app has to answer "how many bags?" /// and "which bag is this?", and every screen that answered it independently /// answered it differently: one showed a backend `Quantity` (an order's item /// count, not a bag count), one showed a bare `baglabel` when the payload /// happened to carry one and nothing at all when it did not, and the kitchen /// heading counted "meals". A rider standing at a counter comparing "5 meals" /// on his phone with four bags on the shelf has no way to tell which of the two /// numbers is wrong. /// /// So the count is **derived from the orders**, always, and there is no second /// quantity anywhere that can disagree with it. /// /// ── What is derived, and what is never invented ── /// /// • **The count** is `orders.length`. It cannot drift, because it is not /// stored — a bag is what an order arrives in. /// • **The identity** prefers the label the backend printed on the physical /// bag ([stopBagLabel]) and falls back to the order's **position in its own /// pickup group** — `Bag 1`, `Bag 2` — which is what a rider counting a /// shelf actually uses. /// /// Nothing here fabricates a crate, a tote or a quantity the backend has not /// sent. If a future contract ever puts more than one bag on an order, this is /// the one file that changes. /// ───────────────────────────────────────────────────────────────────────── class BagManifest { BagManifest._(); /// One line of a pickup manifest: the order, the customer it is for, and the /// bag it travels in. /// /// Deliberately carries the stop itself: every caller that renders a manifest /// also needs to act on the orders behind it, and pairing them here is what /// stops a screen from rendering five lines and posting four ids. static List forGroup(List> stops) => [ for (var i = 0; i < stops.length; i++) BagLine( stop: stops[i], orderId: MilkRun.idOf(stops[i]), customer: customerOf(stops[i]), bag: _bagFor(stops[i], i), ), ]; /// The bag one order travels in, given the group it was collected with. /// /// [stops] must be the whole pickup group in route order — the position in it /// *is* the bag number when the backend has not printed one. static String bagFor( Map stop, List> group, ) { final id = MilkRun.idOf(stop); final index = group.indexWhere((s) => MilkRun.idOf(s) == id); return _bagFor(stop, index < 0 ? 0 : index); } static String _bagFor(Map stop, int index) { final printed = stopBagLabel(stop); return printed.isNotEmpty ? printed : 'Bag ${index + 1}'; } /// `5 bags` — the load, in the unit the rider actually carries. /// /// It used to print `5 orders · 5 bags`: the two halves of the one-bag-per- /// order rule side by side, as a check the rider could eyeball. On a device /// that check cost the header the fact it exists for — a real kitchen name /// plus `13 orders · 13 bags` pushed `11 to accept` off the row, and the /// clause that truncated was the one that changes what he does next. /// /// One number survives, and it is the physical one: the rows underneath are /// named `Bag 1 … Bag n` and a settled group says `n bags collected`, so /// "bags" is the word this column already speaks. No information is lost — /// the rule makes the counts identical — and the verifiable statement lives /// where the verifying happens: the manifest list itself, one line per bag. static String countLabel(int orders) { return orders == 1 ? '1 bag' : '$orders bags'; } /// The name the bag is going to, for a manifest line. static String customerOf(Map stop) => (stop['pickupcustomer'] ?? stop['customername'] ?? stop['tenantname'] ?? '') .toString() .trim(); } /// One row of a pickup manifest. See [BagManifest.forGroup]. class BagLine { final Map stop; final String orderId; final String customer; final String bag; const BagLine({ required this.stop, required this.orderId, required this.customer, required this.bag, }); }