/** The booking fields that decide who hauls the road legs. */ export interface MileHaulageRow { tradeDirection: string | null; /** `first_mile_pickup_address` — set when the customer asked EDR to collect. */ firstMile: string | null; /** `last_mile_delivery_address` — set when the customer asked EDR to deliver. */ lastMile: string | null; } /** * Whether the customer bought the EDR road leg that matters for their direction: * delivery at the end of an import, collection at the start of an export. A * DOMESTIC booking can use either, so either one counts. * * The address is the signal because it is the only per-booking record of the * choice. `service_types.includes_first_mile` / `includes_last_mile` cannot be * used — every service type ships with both set to true, so reading them would * mean every booking uses EDR haulage and none could ever self-haul. */ export function usesEdrMileService(booking: MileHaulageRow): boolean { const hasFirstMile = Boolean(booking.firstMile?.trim()); const hasLastMile = Boolean(booking.lastMile?.trim()); switch (booking.tradeDirection) { case 'IMPORT': return hasLastMile; case 'EXPORT': return hasFirstMile; default: return hasFirstMile || hasLastMile; } } /** * EDR haulage and a customer's own truck are alternatives, never both. Whichever * side is being set up, it has to reject the other — a guard on only one side * lets the two paths open on the same booking, each unaware of the other. */ export const SELF_HAUL_CONFLICT_MESSAGE = 'This booking is delivered by the customer’s own truck — an EDR mile leg cannot also be assigned.'; export const EDR_HAULAGE_CONFLICT_MESSAGE = 'Customer truck assignment is only allowed when first/last mile delivery is not selected, or when the EDR last-mile request has not been approved'; /** The booking fields that decide whether the customer may still bring their own truck. */ export interface MileCommitmentRow extends MileHaulageRow { /** * EDR has actually committed to the delivery leg: the booking's last-mile * request was approved, or a `freight.last_mile` leg row exists for it. * Selecting delivery on the contract is only a request — see * `edrHaulsThisBooking`. */ lastMileCommitted: boolean; } /** * SQL for `MileCommitmentRow.lastMileCommitted`, to be selected alongside the * booking row aliased `b`. Both services that gate self-haul read the same * fragment so the rule cannot drift between them. */ export const LAST_MILE_COMMITTED_SQL = `( EXISTS (SELECT 1 FROM freight.last_mile lm WHERE lm.booking_id = b.id AND lm.deleted_at IS NULL) OR EXISTS (SELECT 1 FROM freight.last_mile_requests lmr WHERE lmr.booking_id = b.id AND lmr.deleted_at IS NULL AND lmr.status = 'APPROVED') )`; /** * Whether EDR is hauling this booking's road leg, such that the customer may * NOT assign their own truck. Stricter than `usesEdrMileService` on the * delivery side: choosing last-mile delivery on the contract opens a request * that the Truck & Machinery chief still has to approve, and until that * approval the customer is free to self-haul instead. Collection (the export * leg) has no approval step, so the contract choice alone decides it. * * `usesEdrMileService` keeps answering the other question — whether the booking * belongs in the EDR mile queues at all — and the queue side still refuses a * booking that already carries a customer truck, so the two paths remain * mutually exclusive whichever acts first. */ export function edrHaulsThisBooking(booking: MileCommitmentRow): boolean { const hasFirstMile = Boolean(booking.firstMile?.trim()); const lastMileApproved = Boolean(booking.lastMile?.trim()) && booking.lastMileCommitted; switch (booking.tradeDirection) { case 'IMPORT': return lastMileApproved; case 'EXPORT': return hasFirstMile; default: return hasFirstMile || lastMileApproved; } } /** * The road legs are chosen on the contract. A booking whose contract bought * neither has no business in the first/last-mile queues at all. */ export const NO_MILE_SERVICE_MESSAGE = 'This booking did not select first/last mile delivery on its contract, so it cannot be assigned an EDR mile leg.';