mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 23:00:57 +00:00
Fix customer_truck_containers status
This commit is contained in:
@@ -1424,12 +1424,14 @@ export class BookingsService {
|
||||
schedule?.status ?? null;
|
||||
}
|
||||
|
||||
// A generated-but-unsigned handover means the customer must approve delivery.
|
||||
// Surfaced so the portal shows "Approve delivery" as soon as the handover
|
||||
// exists, independent of the truck-arrival flag.
|
||||
// A generated-but-unsigned SELF_HAUL handover means the customer must approve
|
||||
// delivery from the portal (booking-based, one per booking). EDR last-mile
|
||||
// handovers are per delivering truck and signed by the receiver at the door,
|
||||
// so they never surface the portal "Approve delivery" action.
|
||||
const [pendingHandover] = await this.dataSource.query(
|
||||
`SELECT 1 FROM freight.booking_handovers
|
||||
WHERE booking_id = $1 AND signed_at IS NULL AND deleted_at IS NULL
|
||||
AND mile_type = 'SELF_HAUL'
|
||||
LIMIT 1`,
|
||||
[id],
|
||||
);
|
||||
|
||||
@@ -312,6 +312,13 @@ export class CustomerTruckService {
|
||||
if (assignment.departedAt) {
|
||||
throw new ConflictException('This truck has already left — its load is locked');
|
||||
}
|
||||
// Containers can only be loaded after the truck has physically arrived at the
|
||||
// warehouse (arrival weighing recorded). Assignment alone is just planning.
|
||||
if (!assignment.arrivedAt) {
|
||||
throw new BadRequestException(
|
||||
'Record the truck arrival before loading — containers can only be loaded onto an arrived truck',
|
||||
);
|
||||
}
|
||||
|
||||
const requested = (dto.containerNumbers ?? []).map((n) => n.trim().toUpperCase());
|
||||
if (!requested.length) {
|
||||
@@ -333,12 +340,16 @@ export class CustomerTruckService {
|
||||
const grossTons = await this.vgmTonsForContainers(bookingId, requested);
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
await manager.getRepository(CustomerTruckContainer).softDelete({ assignmentId });
|
||||
// Operator loading the truck: stamp loaded_at so these containers move to
|
||||
// the LOADED stage (customer assignment alone leaves loaded_at null).
|
||||
const loadedAt = new Date();
|
||||
await manager.getRepository(CustomerTruckContainer).save(
|
||||
requested.map((containerNumber) =>
|
||||
manager.getRepository(CustomerTruckContainer).create({
|
||||
assignmentId,
|
||||
bookingId,
|
||||
containerNumber,
|
||||
loadedAt,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -23,4 +23,12 @@ export class CustomerTruckContainer extends BaseEntity {
|
||||
|
||||
@Column({ name: 'container_number', type: 'varchar', length: 64 })
|
||||
containerNumber!: string;
|
||||
|
||||
/**
|
||||
* When the container was actually loaded onto the truck by the operator.
|
||||
* Null = customer-assigned (planned) but not yet loaded. Stage LOADED requires
|
||||
* this to be set, so customer assignment alone does not mark a container loaded.
|
||||
*/
|
||||
@Column({ name: 'loaded_at', type: 'timestamptz', nullable: true })
|
||||
loadedAt?: Date | null;
|
||||
}
|
||||
|
||||
@@ -2603,12 +2603,13 @@ export class WarehouseInventoryService {
|
||||
Array<{
|
||||
containerNumber: string;
|
||||
goods: string | null;
|
||||
stage: 'PENDING' | 'RECEIVED' | 'GRN' | 'LOADED' | 'LEFT' | 'DELIVERED';
|
||||
stage: 'PENDING' | 'RECEIVED' | 'GRN' | 'ASSIGNED' | 'LOADED' | 'LEFT' | 'DELIVERED';
|
||||
grnNumber: string | null;
|
||||
truckAssignmentId: string | null;
|
||||
truckPlate: string | null;
|
||||
truckArrived: boolean;
|
||||
truckLeft: boolean;
|
||||
loaded: boolean;
|
||||
bookingReference: string | null;
|
||||
contractId: string | null;
|
||||
hasLastMile: boolean;
|
||||
@@ -2624,6 +2625,7 @@ export class WarehouseInventoryService {
|
||||
truckPlate: string | null;
|
||||
truckArrived: boolean;
|
||||
truckLeft: boolean;
|
||||
loaded: boolean;
|
||||
bookingReference: string | null;
|
||||
contractId: string | null;
|
||||
hasLastMile: boolean;
|
||||
@@ -2637,6 +2639,7 @@ export class WarehouseInventoryService {
|
||||
a.plate_number AS "truckPlate",
|
||||
(a.arrived_at IS NOT NULL) AS "truckArrived",
|
||||
(a.departed_at IS NOT NULL) AS "truckLeft",
|
||||
(ctc.loaded_at IS NOT NULL) AS loaded,
|
||||
b.reference AS "bookingReference",
|
||||
b.contract_id AS "contractId",
|
||||
(b.last_mile_delivery_address IS NOT NULL) AS "hasLastMile",
|
||||
@@ -2666,22 +2669,28 @@ export class WarehouseInventoryService {
|
||||
return rows.map((r) => ({
|
||||
containerNumber: r.containerNumber,
|
||||
goods: r.goods,
|
||||
// A container the customer assigned to a truck is ASSIGNED (planned); it
|
||||
// only becomes LOADED once the operator loads it (loaded_at) on truck
|
||||
// leaving. Departed → LEFT, delivered → DELIVERED.
|
||||
stage: r.delivered
|
||||
? 'DELIVERED'
|
||||
: r.truckLeft
|
||||
? 'LEFT'
|
||||
: r.truckAssignmentId
|
||||
: r.loaded
|
||||
? 'LOADED'
|
||||
: r.grnNumber
|
||||
? 'GRN'
|
||||
: r.received
|
||||
? 'RECEIVED'
|
||||
: 'PENDING',
|
||||
: r.truckAssignmentId
|
||||
? 'ASSIGNED'
|
||||
: r.grnNumber
|
||||
? 'GRN'
|
||||
: r.received
|
||||
? 'RECEIVED'
|
||||
: 'PENDING',
|
||||
grnNumber: r.grnNumber,
|
||||
truckAssignmentId: r.truckAssignmentId,
|
||||
truckPlate: r.truckPlate,
|
||||
truckArrived: r.truckArrived,
|
||||
truckLeft: r.truckLeft,
|
||||
loaded: r.loaded,
|
||||
bookingReference: r.bookingReference,
|
||||
contractId: r.contractId,
|
||||
hasLastMile: r.hasLastMile,
|
||||
@@ -3257,7 +3266,22 @@ export class WarehouseInventoryService {
|
||||
[item.bookingId],
|
||||
);
|
||||
} else {
|
||||
await this.handover.ensureAtDelivery(item.bookingId, {}, manager);
|
||||
// EDR last-mile: the handover is per delivering truck. Resolve the
|
||||
// vehicle that carried this item's container so each truck gets its own
|
||||
// handover (falls back to a booking-level one when unresolvable).
|
||||
let truckPlate: string | null = null;
|
||||
if (item.containerId) {
|
||||
const [veh]: Array<{ plate: string | null }> = await manager.query(
|
||||
`SELECT COALESCE(v.power_plate_no, v.plate_number) AS plate
|
||||
FROM freight.last_mile_container_allocations lca
|
||||
JOIN freight.vehicles v ON v.id = lca.vehicle_id
|
||||
WHERE lca.container_id = $1 AND lca.vehicle_id IS NOT NULL
|
||||
LIMIT 1`,
|
||||
[item.containerId],
|
||||
);
|
||||
truckPlate = veh?.plate ?? null;
|
||||
}
|
||||
await this.handover.ensureAtDelivery(item.bookingId, { truckPlate }, manager);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user