// ==============================|| Routing decision, in words ||============================== // // // `GET /admin/bookings/:id` returns a `routing` block: the decision the backend // took about where a parcel goes, and the inputs it took it from. This turns // that into the sentences the order drawer shows. // // It exists so support can answer "why does the rider's screen say hand over at // a base instead of deliver to the customer?" as a lookup instead of // reconstructing a pincode rule by hand on the phone. // // TWO RULES THIS FILE EXISTS TO HOLD: // // 1. A PROJECTION IS NOT A DECISION. Before pickup, nothing has been decided — // `routing.decided` is false and what is shown is what WILL happen. Saying // "this went via a base" about a parcel nobody has collected yet is a // wrong answer delivered confidently, which is worse than no answer. // // 2. THE WIRE SAYS HUB, PEOPLE SAY BASE. The translation happens here, at the // edge, and never travels back up: every value sent to the backend keeps // its wire spelling. /** What the rider does next with the parcel. Keys are the backend's wire * values; an unrecognised one is shown as itself rather than swallowed — * the backend may add actions, and a blank row is worse than a raw word. */ export const NEXT_ACTION_LABEL = Object.freeze({ pickup: 'Collect from the pickup point', inward_at_hub: 'Carry to a base and hand over', start_delivery: 'Start the delivery run', deliver: 'Deliver to the receiver', handed_to_hub: 'Handed over at the base', none: 'Nothing further for the rider' }); export const nextActionLabel = (action) => NEXT_ACTION_LABEL[action] || action || '—'; /** Why the parcel is going the way it is, in one sentence an operator can read * to a rider over the phone. */ export const routingReason = (routing) => { if (!routing) return ''; return routing.is_hyperlocal ? 'Same postal area — the collecting rider carries it straight to the receiver.' : 'Different postal area — it goes through a base rather than direct to the receiver.'; }; /** The from → to line, with the honesty caveat when nothing has been decided * yet. A booking not yet collected has no routing FACT, only a forecast. */ export const routingRoute = (routing) => { if (!routing) return ''; const from = routing.from_pincode || '—'; const to = routing.destination_pincode || '—'; const line = `${from} → ${to}`; return routing.decided ? line : `${line} · not yet collected, so this is what will happen, not what has`; }; /** The base a parcel is routed to, as one readable line. Null when no base is * involved — which is the correct answer for a hyperlocal parcel, not a gap. */ export const baseLine = (base) => { if (!base || !base.name) return null; return base.pincode ? `${base.name} · ${base.pincode}` : base.name; }; /** * Everything the drawer renders, in one call, so the component holds no * decisions of its own. */ export const summariseRouting = (routing) => { if (!routing) return null; return { decided: Boolean(routing.decided), reason: routingReason(routing), route: routingRoute(routing), nextAction: nextActionLabel(routing.next_action), base: baseLine(routing.next_hub), baseAddress: routing.next_hub?.address || null, consignmentState: routing.consignment_state || '—', inwardedAt: routing.inwardedat || null }; };