feat(warehouses): per-truck EDR handover signing with auto-delivery on completion

EDR truck gate-out now generates a per-truck handover (new
edr_assignment_id link) and notifies the customer to sign from the
portal. New sign endpoint delivers the signed truck's containers; last
signature auto-delivers remaining inventory, frees trucks and completes
the booking via import.handover.completed. Adds gate-in truck-arrival
notification and per-truck handover PDFs.
This commit is contained in:
Hagernesh
2026-07-21 11:54:35 +00:00
parent 86569f7490
commit 7566c57ed9

View File

@@ -4429,22 +4429,41 @@ export class WarehouseInventoryService {
);
}
}
// EDR last-mile: same per-truck rule — each assigned truck is weighed out
// separately, and deliver waits until the last one has left.
const [lm]: Array<{ total: string; left: string }> = await this.dataSource.query(
`SELECT COUNT(*) AS total,
COUNT(*) FILTER (WHERE va.departed_at IS NOT NULL) AS "left"
FROM freight.last_mile_vehicle_assignments va
JOIN freight.last_mile l ON l.id = va.last_mile_id AND l.deleted_at IS NULL
WHERE l.booking_id = $1 AND va.deleted_at IS NULL`,
[item.bookingId],
);
const lmTotal = Number(lm?.total ?? 0);
const lmLeft = Number(lm?.left ?? 0);
if (lmTotal > 0 && lmLeft < lmTotal) {
throw new BadRequestException(
`Deliver is available only after every assigned EDR truck has left (${lmLeft} of ${lmTotal} so far)`,
// EDR last-mile delivers per truck: a container item only needs the truck
// CARRYING IT to have left; bulk (no container) waits for every truck.
if (item.containerId) {
const [own]: Array<{ pending: string }> = await this.dataSource.query(
`SELECT COUNT(*) FILTER (WHERE va.departed_at IS NULL) AS pending
FROM freight.last_mile_vehicle_assignments va
JOIN freight.last_mile l ON l.id = va.last_mile_id AND l.deleted_at IS NULL
LEFT JOIN freight.last_mile_vehicle_containers vc
ON vc.assignment_id = va.id AND vc.deleted_at IS NULL
JOIN freight.containers c ON c.id = $2 AND c.deleted_at IS NULL
WHERE l.booking_id = $1 AND va.deleted_at IS NULL
AND COALESCE(vc.container_number, va.container_number) = c.container_number`,
[item.bookingId, item.containerId],
);
if (Number(own?.pending ?? 0) > 0) {
throw new BadRequestException(
'Deliver is available only after the EDR truck carrying this container has left',
);
}
} else {
const [lm]: Array<{ total: string; left: string }> = await this.dataSource.query(
`SELECT COUNT(*) AS total,
COUNT(*) FILTER (WHERE va.departed_at IS NOT NULL) AS "left"
FROM freight.last_mile_vehicle_assignments va
JOIN freight.last_mile l ON l.id = va.last_mile_id AND l.deleted_at IS NULL
WHERE l.booking_id = $1 AND va.deleted_at IS NULL`,
[item.bookingId],
);
const lmTotal = Number(lm?.total ?? 0);
const lmLeft = Number(lm?.left ?? 0);
if (lmTotal > 0 && lmLeft < lmTotal) {
throw new BadRequestException(
`Deliver is available only after every assigned EDR truck has left (${lmLeft} of ${lmTotal} so far)`,
);
}
}
}