mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
The rule that a customer's own truck and an EDR road leg are alternatives existed on the truck side only (CustomerTruckService.assertSelfHaulPaid). LastMileService had no counterpart: create checked payment and nothing else, so any paid booking could be accepted into the queue. A booking took a customer truck at 06:42 and an EDR last-mile leg with a real EDR truck at 06:47, neither side aware of the other, on a contract that had chosen no road legs at all. The road legs are chosen on the contract and copied onto the booking, and the pickup/delivery address is the only per-booking record of that choice. service_types cannot serve: every type ships with includes_first_mile and includes_last_mile set to true, so reading them would mean no booking could ever self-haul. That same always-true flag had already killed the first-mile guard, whose `address || serviceType.includesFirstMile` admitted every paid export booking. One shared rule now answers it for both sides, so the two halves cannot drift apart again: last-mile create rejects a booking that chose no road legs and one already carrying a customer truck; first-mile no longer honours the service-type flag; the customer-truck guard reads the same helper. Existing legs are untouched — the guards are on creation, so the one booking already carrying both needs a human to reconcile it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
/** 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';
|
||
|
||
/**
|
||
* 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.';
|