Files
doormilxpress_astryx/src/lib/routingSummary.js

79 lines
3.4 KiB
JavaScript

// ==============================|| 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
};
};