mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
feat(last-mile): per-truck EDR arrival/exit, exit paper, bulk drawdown, departure notify
EDR last-mile is multi-truck but was modelled as one: setVehicles accepted any number of trucks with no validation, arrival/delivery were stamped once per last_mile record (N trucks shared one timestamp), EDR trucks got no exit paper, and the per-truck EDR handover never happened because deliver() resolved the plate via last_mile_container_allocations — a table nothing writes. - Migration 2260000000000: per-truck arrived_at/departed_at/gross_weight_tons/ net_weight_tons on last_mile_vehicle_assignments, plus a last_mile_vehicle_containers child table (a truck holds 1x40ft OR 2x20ft, so the single container_number scalar could not express a load). Weights are TONNES and named accordingly — the older gross_weight_kg lies about its unit. - setVehicles: enforce the size rule (one 40ft, or two 20ft), container membership, one-container-one-truck, and never more trucks than containers. Bulk carries no containers and is instead gated on tonnage remaining. - Bulk drawdown: remainingTonsForBooking = booking VGM minus the net weighed off every departed truck (both tonnes, no conversion), exposed as GET /last-mile/booking/:bookingId/remaining-tons. - release() now stamps the EDR truck's own arrival and exit (matched by plate, so it works for bulk too) alongside the existing customer-truck stamp. The exit weighing itself is untouched. - New GET /warehouse-inventory/edr-truck-exit-paper/:assignmentId — per-truck exit paper for EDR trucks. Deliberately not signature-gated: EDR handovers are generated at delivery, after the truck has left. - deliver(): resolve the handover plate from the truck's own containers instead of the dead allocations table, so EDR handovers are genuinely per-truck. - Notify the customer (portal inbox + SMS/email) when a truck leaves — one hook in release() covers both self-haul and EDR, since it is the single exit path. Self-haul is intentionally unchanged (one booking-level handover signed once). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* EDR last-mile is multi-truck: a booking can be served by as many trucks as it
|
||||
* has containers (bulk hauls until the tonnage is drawn down). Arrival/delivery
|
||||
* were stamped once per `last_mile` record, so every truck shared one timestamp.
|
||||
* These per-vehicle columns give each EDR truck its own arrival, leaving and
|
||||
* weighed load — the same granularity self-haul trucks already have.
|
||||
*
|
||||
* Weights are TONNES (matching bookings.cargo_total_weight_vgm and the exit
|
||||
* weighing UI). Named `*_tons` deliberately: the older
|
||||
* customer_truck_assignments.gross_weight_kg is named kg but stores tonnes.
|
||||
* All nullable — legacy rows predate per-truck tracking.
|
||||
*/
|
||||
export class AddLastMileTruckArrivalDeparture2260000000000 implements MigrationInterface {
|
||||
name = 'AddLastMileTruckArrivalDeparture2260000000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.last_mile_vehicle_assignments
|
||||
ADD COLUMN IF NOT EXISTS arrived_at timestamptz NULL,
|
||||
ADD COLUMN IF NOT EXISTS departed_at timestamptz NULL,
|
||||
ADD COLUMN IF NOT EXISTS gross_weight_tons numeric(14, 3) NULL,
|
||||
ADD COLUMN IF NOT EXISTS net_weight_tons numeric(14, 3) NULL
|
||||
`);
|
||||
|
||||
// A truck carries 1x40ft OR 2x20ft, so an EDR truck needs MORE than the one
|
||||
// container the legacy scalar `container_number` can hold. Mirrors the
|
||||
// self-haul customer_truck_containers child table. The scalar stays in place
|
||||
// (synced to the first container) for backward compatibility.
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS freight.last_mile_vehicle_containers (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
assignment_id uuid NOT NULL REFERENCES freight.last_mile_vehicle_assignments(id) ON DELETE CASCADE,
|
||||
last_mile_id uuid NOT NULL,
|
||||
container_number varchar(32) NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_last_mile_vehicle_containers_assignment"
|
||||
ON freight.last_mile_vehicle_containers (assignment_id)
|
||||
`);
|
||||
// A container rides exactly one truck per delivery.
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_last_mile_vehicle_container"
|
||||
ON freight.last_mile_vehicle_containers (last_mile_id, container_number)
|
||||
WHERE deleted_at IS NULL
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS freight.last_mile_vehicle_containers`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.last_mile_vehicle_assignments
|
||||
DROP COLUMN IF EXISTS arrived_at,
|
||||
DROP COLUMN IF EXISTS departed_at,
|
||||
DROP COLUMN IF EXISTS gross_weight_tons,
|
||||
DROP COLUMN IF EXISTS net_weight_tons
|
||||
`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user