Files
doormile_milderapp/lib/data/order_manifest.dart
2026-08-28 15:07:30 +05:30

115 lines
5.4 KiB
Dart

import 'package:miler/data/milk_run.dart';
import 'package:miler/views/Dashboard/pickups/stop_type.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// WHAT THE RIDER COLLECTS AT A COUNTER, AS A LIST HE CAN COUNT
///
/// One pickup order is one handover. If a kitchen has five pickup orders the
/// rider is handed five things and ends up with five deliveries, each carrying
/// what it arrived in. That rule lives here so it cannot be stated two
/// different ways on two screens.
///
/// ── 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?" and
/// "which one is this?", and every screen that answered independently answered
/// differently: one showed a backend `Quantity` (an order's item count, not a
/// count of handovers), one showed a bare printed label 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 items 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.
///
/// ── The invented numbering is gone ──
///
/// This used to name every line `Bag 1 … Bag n`, falling back to the order's
/// position in its group whenever the payload printed no label of its own. Two
/// things were wrong with that, and only the second is about the word.
///
/// The number was **the app's own invention presented as a physical fact**. It
/// counted positions in a list the app had built, and it was drawn as a tag in
/// the corner of every row where it read like something stencilled on a
/// container. A rider matching a shelf against it was matching against the
/// app's arithmetic, not against anything the kitchen had written; and the rail
/// beside it already numbers the same rows, so the tag mostly restated the node
/// two columns to its left in louder type.
///
/// A label the counter **actually printed** is a different thing entirely — it
/// is a fact about an object in front of him — so it survives, verbatim, under
/// whatever name the kitchen gave it. What is never done again is manufacturing
/// one where none exists.
///
/// Nothing here fabricates a crate, a tote or a quantity the backend has not
/// sent. If a future contract ever puts more than one handover on an order,
/// this is the one file that changes.
/// ─────────────────────────────────────────────────────────────────────────
class OrderManifest {
OrderManifest._();
/// One line per order: the stop, who it is for, and the label the counter
/// printed on it — empty when it printed none.
///
/// 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<ManifestLine> forGroup(List<Map<String, dynamic>> stops) => [
for (var i = 0; i < stops.length; i++)
ManifestLine(
stop: stops[i],
orderId: MilkRun.idOf(stops[i]),
customer: customerOf(stops[i]),
label: labelFor(stops[i]),
),
];
/// The label physically printed on one order's handover, or `''`.
///
/// Read straight off the payload and never derived. A counter that prints
/// nothing leaves this empty, and the row simply carries no tag — which is
/// honest, where a manufactured `Bag 4` was a shelf reference the shelf had
/// never heard of.
static String labelFor(Map<String, dynamic> stop) => stopPrintedLabel(stop);
/// `5 orders` — the load, in the unit everything else on the screen counts.
///
/// It printed `5 orders · 5 bags` once: the two halves of one rule side by
/// side, as a check the rider could eyeball. That cost the header the fact it
/// exists for — a real kitchen name plus `13 orders · 13 bags` pushed
/// `11 to accept` off the row — so it was cut to the physical half, `13 bags`.
///
/// Now that the app no longer names anything a bag, the surviving half is the
/// one the rest of the screen already speaks: the rows underneath are orders,
/// the chip counts orders, the hub assigns orders. One word, everywhere.
static String countLabel(int orders) =>
orders == 1 ? '1 order' : '$orders orders';
/// The name the order is going to, for a manifest line.
static String customerOf(Map<String, dynamic> stop) =>
(stop['pickupcustomer'] ??
stop['customername'] ??
stop['tenantname'] ??
'')
.toString()
.trim();
}
/// One row of a pickup manifest. See [OrderManifest.forGroup].
class ManifestLine {
final Map<String, dynamic> stop;
final String orderId;
final String customer;
/// What the counter printed on this order, or `''` when it printed nothing.
final String label;
const ManifestLine({
required this.stop,
required this.orderId,
required this.customer,
this.label = '',
});
}