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:
Hagernesh
2026-07-16 08:30:18 +00:00
parent a85bae1002
commit ff49554cc2
9 changed files with 547 additions and 29 deletions

View File

@@ -421,6 +421,20 @@ export class WarehouseInventoryController {
return res.send(buffer);
}
@Get('edr-truck-exit-paper/:assignmentId')
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
@ApiOperation({ summary: 'Per-truck exit paper PDF for an EDR last-mile truck' })
async edrTruckExitPaper(
@Param('assignmentId', ParseUUIDPipe) assignmentId: string,
@Res() res: Response,
) {
const { filename, buffer } = await this.inventoryService.edrTruckExitPaper(assignmentId);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
res.setHeader('Content-Length', buffer.length);
return res.send(buffer);
}
@Get(':id/grn-document')
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
@ApiOperation({ summary: 'View goods received note PDF' })