mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix(warehouses): exit-paper truck weight from summed container VGM
Per-truck weight now sums the departing truck's container vgm_tons (recorded net for bulk); weighed gross only as fallback.
This commit is contained in:
@@ -3347,6 +3347,17 @@ export class WarehouseInventoryService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* customer_truck_assignments.gross_weight_kg holds TONNES for gate-out
|
||||
* recorded exits but real KG for legacy departTruck rows. Exit papers always
|
||||
* print tonnes — normalise on read.
|
||||
*/
|
||||
// ponytail: >1000 heuristic (no truck hauls 1000+ t, no weighbridge reads <1000 kg);
|
||||
// migrate the column to tonnes if it ever bites.
|
||||
private grossAsTons(value: number): number {
|
||||
return value > 1000 ? Math.round(value) / 1000 : value;
|
||||
}
|
||||
|
||||
async releaseDocument(id: string): Promise<{ filename: string; buffer: Buffer }> {
|
||||
const [row] = await this.dataSource.query(
|
||||
`SELECT inv.id,
|
||||
@@ -3406,7 +3417,40 @@ export class WarehouseInventoryService {
|
||||
grossWeightKg: string | number | null;
|
||||
departedAt: string | null;
|
||||
} | null = null;
|
||||
if (row?.tradeDirection === 'IMPORT' && row?.containerNumber && row?.bookingId) {
|
||||
// The exit-inspection note written at gate-out names the truck doing THIS
|
||||
// exit — resolve by its plate first. The item's own container may not be on
|
||||
// the departing truck at all (trucks pick containers freely per trip).
|
||||
const notePlates = [...String(row?.notes ?? '').matchAll(/Truck Plate:\s*(\S+)/gi)];
|
||||
const exitPlate = notePlates.length ? notePlates[notePlates.length - 1][1] : null;
|
||||
if (row?.tradeDirection === 'IMPORT' && row?.bookingId && exitPlate) {
|
||||
const [truckRow] = await this.dataSource.query(
|
||||
`SELECT a.plate_number AS "plateNumber",
|
||||
a.driver_name AS "driverName",
|
||||
a.truck_type AS "truckType",
|
||||
a.gross_weight_kg AS "grossWeightKg",
|
||||
a.departed_at AS "departedAt",
|
||||
string_agg(DISTINCT c.container_number, ', ' ORDER BY c.container_number) AS "containerNumbers",
|
||||
COALESCE((
|
||||
SELECT SUM(bcu.vgm_tons)
|
||||
FROM freight.customer_truck_containers cc
|
||||
JOIN freight.booking_container_units bcu
|
||||
ON bcu.container_number = cc.container_number AND bcu.deleted_at IS NULL
|
||||
JOIN freight.booking_container bc
|
||||
ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL
|
||||
AND bc.booking_id = a.booking_id
|
||||
WHERE cc.assignment_id = a.id AND cc.deleted_at IS NULL
|
||||
), 0) AS "truckWeightTons"
|
||||
FROM freight.customer_truck_assignments a
|
||||
LEFT JOIN freight.customer_truck_containers c
|
||||
ON c.assignment_id = a.id AND c.deleted_at IS NULL
|
||||
WHERE a.booking_id = $1 AND UPPER(a.plate_number) = UPPER($2) AND a.deleted_at IS NULL
|
||||
GROUP BY a.id, a.plate_number, a.driver_name, a.truck_type, a.gross_weight_kg, a.departed_at
|
||||
LIMIT 1`,
|
||||
[row.bookingId, exitPlate],
|
||||
);
|
||||
truck = truckRow ?? null;
|
||||
}
|
||||
if (!truck && row?.tradeDirection === 'IMPORT' && row?.containerNumber && row?.bookingId) {
|
||||
const [truckRow] = await this.dataSource.query(
|
||||
`SELECT a.plate_number AS "plateNumber",
|
||||
a.driver_name AS "driverName",
|
||||
@@ -3436,6 +3480,26 @@ export class WarehouseInventoryService {
|
||||
);
|
||||
truck = truckRow ?? null;
|
||||
}
|
||||
// Bulk self-haul (no container to match) or an unmatched container: the exit
|
||||
// paper is still PER TRUCK — use the latest departed customer truck and its
|
||||
// weighed gross, never the booking's declared total.
|
||||
if (!truck && row?.tradeDirection === 'IMPORT' && row?.bookingId) {
|
||||
const [truckRow] = await this.dataSource.query(
|
||||
`SELECT a.plate_number AS "plateNumber",
|
||||
a.driver_name AS "driverName",
|
||||
a.truck_type AS "truckType",
|
||||
a.gross_weight_kg AS "grossWeightKg",
|
||||
a.departed_at AS "departedAt",
|
||||
NULL AS "containerNumbers",
|
||||
a.net_weight_tons AS "truckWeightTons"
|
||||
FROM freight.customer_truck_assignments a
|
||||
WHERE a.booking_id = $1 AND a.deleted_at IS NULL AND a.departed_at IS NOT NULL
|
||||
ORDER BY a.departed_at DESC
|
||||
LIMIT 1`,
|
||||
[row.bookingId],
|
||||
);
|
||||
truck = truckRow ?? null;
|
||||
}
|
||||
|
||||
const bookingReference = row?.bookingReference || 'N/A';
|
||||
const reference =
|
||||
@@ -3450,7 +3514,9 @@ export class WarehouseInventoryService {
|
||||
customerName: row?.customerName ?? null,
|
||||
freightType: row?.freightType ?? null,
|
||||
tradeDirection: row?.tradeDirection ?? null,
|
||||
containerNumber: row?.containerNumber ?? null,
|
||||
// Per-truck exit: list every container leaving on THIS truck, not just
|
||||
// the inventory item's own container.
|
||||
containerNumber: truck?.containerNumbers ?? row?.containerNumber ?? null,
|
||||
cargoDescription: row?.cargoDescription ?? null,
|
||||
quantity: Number(row?.quantity ?? 0),
|
||||
weight: Number(row?.weight ?? 0),
|
||||
@@ -3464,12 +3530,12 @@ export class WarehouseInventoryService {
|
||||
truckDriverName: truck?.driverName ?? null,
|
||||
truckType: truck?.truckType ?? null,
|
||||
truckGateOut: truck?.departedAt ?? null,
|
||||
// Prefer the weighed gross captured on departure; fall back to the summed
|
||||
// container VGM when the truck hasn't been weighed yet.
|
||||
truckWeightKg: truck
|
||||
? Number(truck.grossWeightKg ?? 0) > 0
|
||||
? Number(truck.grossWeightKg)
|
||||
: Number(truck.truckWeightTons ?? 0) * 1000
|
||||
// Per-truck load in tonnes: the summed VGM of the containers on this truck
|
||||
// (recorded net for bulk); the weighed gross only as fallback.
|
||||
truckWeightTons: truck
|
||||
? Number(truck.truckWeightTons ?? 0) > 0
|
||||
? Number(truck.truckWeightTons)
|
||||
: this.grossAsTons(Number(truck.grossWeightKg ?? 0))
|
||||
: null,
|
||||
});
|
||||
|
||||
@@ -3642,10 +3708,17 @@ export class WarehouseInventoryService {
|
||||
);
|
||||
if (inv?.id) await this.invoices.assertClearanceAllowed(inv.id);
|
||||
|
||||
const containers: Array<{ containerNumber: string; goods: string | null }> =
|
||||
const containers: Array<{ containerNumber: string; goods: string | null; vgmTons: string | null }> =
|
||||
await this.dataSource.query(
|
||||
`SELECT c.container_number AS "containerNumber",
|
||||
COALESCE(ct.cargo_type_name, b.cargo_free_text) AS goods
|
||||
COALESCE(ct.cargo_type_name, b.cargo_free_text) AS goods,
|
||||
(SELECT SUM(u.vgm_tons)
|
||||
FROM freight.booking_container_units u
|
||||
JOIN freight.booking_container bc
|
||||
ON bc.id = u.booking_container_id AND bc.deleted_at IS NULL
|
||||
WHERE u.container_number = c.container_number
|
||||
AND bc.booking_id = c.booking_id
|
||||
AND u.deleted_at IS NULL) AS "vgmTons"
|
||||
FROM freight.customer_truck_containers c
|
||||
JOIN freight.bookings b ON b.id = c.booking_id
|
||||
LEFT JOIN freight.cargo_types ct ON ct.id = b.cargo_type_id
|
||||
@@ -3654,6 +3727,9 @@ export class WarehouseInventoryService {
|
||||
[assignmentId],
|
||||
);
|
||||
|
||||
// Truck load in tonnes: summed container VGM; the weighed gross only as
|
||||
// fallback (bulk trucks carry no containers).
|
||||
const vgmSum = containers.reduce((s, c) => s + (Number(c.vgmTons) || 0), 0);
|
||||
const html = this.buildTruckExitPaperHtml({
|
||||
reference: `REL-${String(truck.bookingReference).replace(/^BK-?/i, '')}-${truck.plateNumber}`,
|
||||
bookingReference: truck.bookingReference,
|
||||
@@ -3661,7 +3737,7 @@ export class WarehouseInventoryService {
|
||||
plateNumber: truck.plateNumber,
|
||||
driverName: truck.driverName,
|
||||
truckType: truck.truckType,
|
||||
grossWeightKg: Number(truck.grossWeightKg ?? 0),
|
||||
grossWeightKg: vgmSum > 0 ? vgmSum : this.grossAsTons(Number(truck.grossWeightKg ?? 0)),
|
||||
gateOut: truck.departedAt,
|
||||
containers,
|
||||
});
|
||||
@@ -5168,7 +5244,7 @@ export class WarehouseInventoryService {
|
||||
truckDriverName?: string | null;
|
||||
truckType?: string | null;
|
||||
truckGateOut?: string | null;
|
||||
truckWeightKg?: number | null;
|
||||
truckWeightTons?: number | null;
|
||||
}): string {
|
||||
const esc = (value: unknown) =>
|
||||
String(value ?? '-')
|
||||
@@ -5195,8 +5271,8 @@ export class WarehouseInventoryService {
|
||||
['Quantity', data.quantity],
|
||||
[
|
||||
data.truckPlateNumber ? 'Gross Weight (Loaded on Truck)' : 'Declared Weight',
|
||||
`${(data.truckPlateNumber && data.truckWeightKg
|
||||
? data.truckWeightKg
|
||||
`${(data.truckPlateNumber && data.truckWeightTons
|
||||
? data.truckWeightTons
|
||||
: data.weight
|
||||
).toLocaleString()} t`,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user