mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
GRN-<DIR>-<DATE>-<REF8> carried no owner, so a note couldn't be identified by who owns the cargo. Add an owner segment sourced from the booking's company at every generation point (import, export, facility, manual receive), keep REF8 for uniqueness, and label the GRN document row Owner's Name.
540 lines
23 KiB
TypeScript
540 lines
23 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { DataSource } from 'typeorm';
|
|
|
|
import { deriveTradeDirection } from '../../common/derive-trade-direction.util';
|
|
|
|
/**
|
|
* READ-ONLY view into the train-scheduling / wagons domain for the warehouse module.
|
|
*
|
|
* IMPORTANT: this facade only ever runs SELECTs. The warehouse must never modify
|
|
* wagon assignment, rescheduling, import_ready/export_ready, or locomotive flow.
|
|
* It is intentionally decoupled (raw SQL) so it does not import the scheduling
|
|
* services/entities and cannot accidentally write to them.
|
|
*/
|
|
export interface ImportTrainRow {
|
|
scheduleId: string;
|
|
trainNumber: string | null;
|
|
route: string | null;
|
|
origin: string | null;
|
|
destination: string | null;
|
|
/** freight.yards.id the train is heading to — lets the frontend restrict the unload warehouse picker to the warehouse actually at this station, instead of listing every warehouse. */
|
|
destinationStationId: string | null;
|
|
arrivalTime: string | null;
|
|
totalBookings: number;
|
|
totalContainers: number;
|
|
totalCargoes: number;
|
|
unloadedBookings: number;
|
|
pendingUnloadBookings: number;
|
|
fullyUnloaded: boolean;
|
|
status: string;
|
|
}
|
|
|
|
export interface ImportTrainItemRow {
|
|
bookingId: string;
|
|
bookingReference: string | null;
|
|
customerId: string | null;
|
|
customerName: string | null;
|
|
wagonNumber: string | null;
|
|
sequenceNo: number | null;
|
|
allocatedWeightTons: number | null;
|
|
freightType: string | null;
|
|
containerNumber: string | null;
|
|
cargoType: string | null;
|
|
/** Cargo type CODE (e.g. "WHEAT"), for matching against a yard's configured cargo types — `cargoType` above is the display name. */
|
|
cargoTypeCode: string | null;
|
|
weight: number | null;
|
|
arrivalTime: string | null;
|
|
currentStatus: string | null;
|
|
inspectionStatus: string | null;
|
|
lastMileRequested: boolean;
|
|
pickupOption: string;
|
|
}
|
|
|
|
export interface ExportDjiboutiQueueFilter {
|
|
scheduleId?: string;
|
|
destination?: string;
|
|
status?: string;
|
|
dateFrom?: string;
|
|
dateTo?: string;
|
|
}
|
|
|
|
export interface ExportTrainRow extends ImportTrainRow {
|
|
departureTime: string | null;
|
|
}
|
|
|
|
export interface ExportTrainItemRow {
|
|
bookingId: string;
|
|
bookingReference: string | null;
|
|
customerId: string | null;
|
|
customerName: string | null;
|
|
wagonNumber: string | null;
|
|
sequenceNo: number | null;
|
|
allocatedWeightTons: number | null;
|
|
itemType: 'CONTAINER' | 'CARGO';
|
|
itemId: string | null;
|
|
inventoryId: string | null;
|
|
containerNumber: string | null;
|
|
cargoType: string | null;
|
|
weight: number | null;
|
|
origin: string | null;
|
|
destination: string | null;
|
|
trainSchedule: string | null;
|
|
arrivalTime: string | null;
|
|
currentStatus: string | null;
|
|
}
|
|
export interface WagonView {
|
|
id: string;
|
|
wagonNumber: string;
|
|
status: string;
|
|
trainId: string | null;
|
|
}
|
|
|
|
export interface BookingScheduleView {
|
|
schedule: {
|
|
id: string;
|
|
status: string;
|
|
scheduledDepartureDate: string | null;
|
|
scheduledArrivalDate: string | null;
|
|
originStationId: string | null;
|
|
destinationStationId: string | null;
|
|
} | null;
|
|
wagon: {
|
|
wagonId: string | null;
|
|
wagonNumber: string | null;
|
|
sequenceNo: number | null;
|
|
allocatedWeightTons: number | null;
|
|
} | null;
|
|
/** Mirror of schedule.status — the headline "where is the train" indicator. */
|
|
departureStatus: string | null;
|
|
}
|
|
|
|
@Injectable()
|
|
export class SchedulingReadFacade {
|
|
constructor(private readonly dataSource: DataSource) {}
|
|
|
|
private isDjiboutiPortDestination(value: string | null | undefined): boolean {
|
|
const normalized = (value ?? '').toUpperCase();
|
|
return ['DJIBOUTI', 'DORALEH', 'DMP', 'DCT', 'NAGAD'].some((token) =>
|
|
normalized.includes(token),
|
|
);
|
|
}
|
|
|
|
/** Look up a single physical wagon. Returns null if it does not exist. */
|
|
async findWagon(wagonId: string): Promise<WagonView | null> {
|
|
const rows = await this.dataSource.query(
|
|
`SELECT id, wagon_number AS "wagonNumber", status, train_id AS "trainId"
|
|
FROM freight.wagons
|
|
WHERE id = $1 AND deleted_at IS NULL
|
|
LIMIT 1`,
|
|
[wagonId],
|
|
);
|
|
return rows?.[0] ?? null;
|
|
}
|
|
|
|
/** True when the wagon is already part of a train set (selected by an existing schedule). */
|
|
async isWagonScheduled(wagonId: string): Promise<boolean> {
|
|
const rows = await this.dataSource.query(
|
|
`SELECT 1 FROM freight.train_set_wagons
|
|
WHERE physical_wagon_id = $1 AND deleted_at IS NULL
|
|
LIMIT 1`,
|
|
[wagonId],
|
|
);
|
|
return (rows?.length ?? 0) > 0;
|
|
}
|
|
|
|
/** List wagons usable for loading (available, or already assigned to a schedule). */
|
|
listLoadableWagons(): Promise<WagonView[]> {
|
|
return this.dataSource.query(
|
|
`SELECT id, wagon_number AS "wagonNumber", status, train_id AS "trainId"
|
|
FROM freight.wagons
|
|
WHERE deleted_at IS NULL
|
|
AND UPPER(status) NOT IN ('DETAINED', 'MAINTENANCE')
|
|
ORDER BY wagon_number ASC`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Given a booking, return its related schedule, wagon assignment and departure status.
|
|
* All fields are read straight from the scheduling tables — nothing is written.
|
|
*/
|
|
async getBookingSchedule(bookingId: string): Promise<BookingScheduleView> {
|
|
const scheduleRows = await this.dataSource.query(
|
|
`SELECT ts.id,
|
|
ts.status,
|
|
ts.scheduled_departure_date AS "scheduledDepartureDate",
|
|
ts.scheduled_arrival_date AS "scheduledArrivalDate",
|
|
ts.origin_station_id AS "originStationId",
|
|
ts.destination_station_id AS "destinationStationId"
|
|
FROM freight.train_schedule_bookings tsb
|
|
INNER JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
|
|
WHERE tsb.booking_id = $1 AND ts.deleted_at IS NULL
|
|
ORDER BY ts.scheduled_departure_date DESC NULLS LAST
|
|
LIMIT 1`,
|
|
[bookingId],
|
|
);
|
|
const schedule = scheduleRows?.[0] ?? null;
|
|
|
|
const wagonRows = await this.dataSource.query(
|
|
`SELECT w.id AS "wagonId",
|
|
w.wagon_number AS "wagonNumber",
|
|
tsw.sequence_no AS "sequenceNo",
|
|
wba.allocated_weight_tons AS "allocatedWeightTons"
|
|
FROM freight.wagon_booking_allocations wba
|
|
INNER JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
|
|
LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id
|
|
WHERE wba.booking_id = $1
|
|
ORDER BY tsw.sequence_no ASC NULLS LAST
|
|
LIMIT 1`,
|
|
[bookingId],
|
|
);
|
|
const wagon = wagonRows?.[0] ?? null;
|
|
|
|
return {
|
|
schedule,
|
|
wagon,
|
|
departureStatus: schedule?.status ?? null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* ARRIVED train schedules whose route is IMPORT (origin country = Djibouti), with per-train
|
|
* booking/container/cargo counts. Direction is derived from the origin/destination station
|
|
* countries (route-based), so EXPORT/DOMESTIC trains never appear. Read-only.
|
|
*/
|
|
async importArriveQueue(): Promise<ImportTrainRow[]> {
|
|
const rows: Array<
|
|
ImportTrainRow & { originCountry: string | null; destinationCountry: string | null }
|
|
> = await this.dataSource.query(
|
|
`SELECT ts.id AS "scheduleId",
|
|
ts.train_number AS "trainNumber",
|
|
oy.code AS "origin",
|
|
dy.code AS "destination",
|
|
dy.id AS "destinationStationId",
|
|
oy.country AS "originCountry",
|
|
dy.country AS "destinationCountry",
|
|
COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS "arrivalTime",
|
|
ts.status,
|
|
(SELECT count(*) FROM freight.train_schedule_bookings tsb
|
|
WHERE tsb.train_schedule_id = ts.id AND tsb.deleted_at IS NULL) AS "totalBookings",
|
|
(SELECT count(*) FROM freight.containers c
|
|
JOIN freight.train_schedule_bookings tsbc ON tsbc.booking_id = c.booking_id AND tsbc.deleted_at IS NULL
|
|
WHERE tsbc.train_schedule_id = ts.id AND c.deleted_at IS NULL) AS "totalContainers",
|
|
(SELECT count(*) FROM freight.cargoes cg
|
|
JOIN freight.train_schedule_bookings tsbg ON tsbg.booking_id = cg.booking_id AND tsbg.deleted_at IS NULL
|
|
WHERE tsbg.train_schedule_id = ts.id AND cg.deleted_at IS NULL) AS "totalCargoes",
|
|
(SELECT count(*) FROM freight.train_schedule_bookings tsbp
|
|
JOIN freight.bookings bp ON bp.id = tsbp.booking_id AND bp.deleted_at IS NULL
|
|
WHERE tsbp.train_schedule_id = ts.id
|
|
AND tsbp.deleted_at IS NULL
|
|
AND bp.status NOT IN ('DRAFT', 'REJECTED', 'CANCELLED', 'COMPLETED')
|
|
AND (
|
|
NOT EXISTS (
|
|
SELECT 1 FROM freight.warehouse_inventory invp
|
|
WHERE invp.booking_id = bp.id AND invp.deleted_at IS NULL
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM freight.warehouse_inventory invr
|
|
WHERE invr.booking_id = bp.id
|
|
AND invr.deleted_at IS NULL
|
|
AND invr.status = 'RECEIVED'
|
|
)
|
|
)) AS "pendingUnloadBookings"
|
|
FROM freight.train_schedules ts
|
|
LEFT JOIN freight.yards oy ON oy.id = ts.origin_station_id
|
|
LEFT JOIN freight.yards dy ON dy.id = ts.destination_station_id
|
|
WHERE ts.deleted_at IS NULL
|
|
AND ts.status = 'ARRIVED'
|
|
ORDER BY COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) DESC NULLS LAST`,
|
|
);
|
|
|
|
return rows
|
|
.filter(
|
|
(r) =>
|
|
deriveTradeDirection({ country: r.originCountry }, { country: r.destinationCountry }) === 'IMPORT',
|
|
)
|
|
.map(({ originCountry: _oc, destinationCountry: _dc, ...rest }) => {
|
|
const totalBookings = Number(rest.totalBookings) || 0;
|
|
const pendingUnloadBookings = Number(rest.pendingUnloadBookings) || 0;
|
|
const unloadedBookings = Math.max(totalBookings - pendingUnloadBookings, 0);
|
|
|
|
return {
|
|
...rest,
|
|
totalBookings,
|
|
totalContainers: Number(rest.totalContainers) || 0,
|
|
totalCargoes: Number(rest.totalCargoes) || 0,
|
|
unloadedBookings,
|
|
pendingUnloadBookings,
|
|
fullyUnloaded: totalBookings > 0 && pendingUnloadBookings === 0,
|
|
route: rest.origin || rest.destination ? `${rest.origin ?? '?'} → ${rest.destination ?? '?'}` : null,
|
|
};
|
|
});
|
|
}
|
|
|
|
/** Assigned bookings/items for an arrived import train (one row per booking). Read-only. */
|
|
async importTrainDetail(scheduleId: string): Promise<ImportTrainItemRow[]> {
|
|
const rows: ImportTrainItemRow[] = await this.dataSource.query(
|
|
`SELECT b.id AS "bookingId",
|
|
b.reference AS "bookingReference",
|
|
b.company_id AS "customerId",
|
|
company.name AS "customerName",
|
|
w.wagon_number AS "wagonNumber",
|
|
tsw.sequence_no AS "sequenceNo",
|
|
wba.allocated_weight_tons AS "allocatedWeightTons",
|
|
b.freight_type AS "freightType",
|
|
(SELECT c.container_number FROM freight.containers c
|
|
WHERE c.booking_id = b.id AND c.deleted_at IS NULL
|
|
ORDER BY c.container_number LIMIT 1) AS "containerNumber",
|
|
COALESCE(cgt.cargo_type_name, b.cargo_free_text) AS "cargoType",
|
|
cgt.code AS "cargoTypeCode",
|
|
b.cargo_total_weight_vgm AS "weight",
|
|
COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS "arrivalTime",
|
|
COALESCE(inv.status, b.status) AS "currentStatus",
|
|
inv.inspection_status AS "inspectionStatus",
|
|
(b.last_mile_delivery_address IS NOT NULL) AS "lastMileRequested",
|
|
CASE WHEN b.last_mile_delivery_address IS NOT NULL
|
|
THEN 'DOOR_DELIVERY' ELSE 'TERMINAL_PICKUP' END AS "pickupOption"
|
|
FROM freight.train_schedule_bookings tsb
|
|
JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
|
|
JOIN freight.bookings b ON b.id = tsb.booking_id AND b.deleted_at IS NULL
|
|
LEFT JOIN freight.wagon_booking_allocations wba ON wba.booking_id = b.id
|
|
AND wba.deleted_at IS NULL
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM freight.train_set_wagons tsw_match
|
|
WHERE tsw_match.id = wba.train_set_wagon_id
|
|
AND tsw_match.train_set_id = ts.train_set_id
|
|
AND tsw_match.deleted_at IS NULL
|
|
)
|
|
LEFT JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
|
|
AND tsw.train_set_id = ts.train_set_id
|
|
AND tsw.deleted_at IS NULL
|
|
LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id AND w.deleted_at IS NULL
|
|
LEFT JOIN freight.companies company ON company.id = b.company_id
|
|
LEFT JOIN freight.cargo_types cgt ON cgt.id = b.cargo_type_id
|
|
LEFT JOIN freight.warehouse_inventory inv ON inv.booking_id = b.id AND inv.deleted_at IS NULL
|
|
WHERE tsb.train_schedule_id = $1 AND tsb.deleted_at IS NULL
|
|
ORDER BY tsw.sequence_no ASC NULLS LAST, b.reference ASC NULLS LAST`,
|
|
[scheduleId],
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* ARRIVED export train schedules at Djibouti-side destinations, with assigned item counts.
|
|
* Read-only: this only selects from scheduling/booking/inventory tables.
|
|
*/
|
|
async exportDjiboutiArrivalQueue(
|
|
filter: ExportDjiboutiQueueFilter = {},
|
|
): Promise<ExportTrainRow[]> {
|
|
const params: unknown[] = [];
|
|
const where = [
|
|
'ts.deleted_at IS NULL',
|
|
"ts.status = ANY($1)",
|
|
`EXISTS (
|
|
SELECT 1
|
|
FROM freight.train_schedule_bookings tsb_exists
|
|
JOIN freight.bookings b_exists ON b_exists.id = tsb_exists.booking_id AND b_exists.deleted_at IS NULL
|
|
LEFT JOIN freight.warehouse_inventory inv_exists ON inv_exists.booking_id = b_exists.id AND inv_exists.deleted_at IS NULL
|
|
WHERE tsb_exists.train_schedule_id = ts.id
|
|
AND tsb_exists.deleted_at IS NULL
|
|
AND (inv_exists.status = ANY($2) OR b_exists.status = ANY($2))
|
|
)`,
|
|
];
|
|
params.push(
|
|
filter.status ? [filter.status] : ['ARRIVED', 'ARRIVED_AT_DJIBOUTI'],
|
|
[
|
|
'LOADED',
|
|
'DISPATCHED',
|
|
'IN_TRANSIT',
|
|
'ARRIVED',
|
|
'ARRIVED_AT_DJIBOUTI',
|
|
'ARRIVED_AT_PORT',
|
|
'ARRIVED_AT_DESTINATION',
|
|
'UNLOADED_AT_DJIBOUTI_PORT',
|
|
],
|
|
);
|
|
|
|
if (filter.scheduleId) {
|
|
params.push(filter.scheduleId);
|
|
where.push(`ts.id = $${params.length}`);
|
|
}
|
|
if (filter.destination) {
|
|
params.push(`%${filter.destination}%`);
|
|
where.push(`(dy.code ILIKE $${params.length} OR dy.label ILIKE $${params.length})`);
|
|
}
|
|
if (filter.dateFrom) {
|
|
params.push(filter.dateFrom);
|
|
where.push(`COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) >= $${params.length}`);
|
|
}
|
|
if (filter.dateTo) {
|
|
params.push(filter.dateTo);
|
|
where.push(`COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) <= $${params.length}`);
|
|
}
|
|
|
|
const rows: Array<
|
|
ExportTrainRow & {
|
|
originCountry: string | null;
|
|
destinationCountry: string | null;
|
|
destinationName: string | null;
|
|
}
|
|
> = await this.dataSource.query(
|
|
`SELECT ts.id AS "scheduleId",
|
|
ts.train_number AS "trainNumber",
|
|
oy.code AS "origin",
|
|
dy.code AS "destination",
|
|
dy.id AS "destinationStationId",
|
|
dy.label AS "destinationName",
|
|
oy.country AS "originCountry",
|
|
dy.country AS "destinationCountry",
|
|
ts.scheduled_departure_date AS "departureTime",
|
|
COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS "arrivalTime",
|
|
ts.status,
|
|
(SELECT count(*) FROM freight.train_schedule_bookings tsb
|
|
WHERE tsb.train_schedule_id = ts.id AND tsb.deleted_at IS NULL) AS "totalBookings",
|
|
(SELECT count(*) FROM freight.containers c
|
|
JOIN freight.train_schedule_bookings tsbc ON tsbc.booking_id = c.booking_id AND tsbc.deleted_at IS NULL
|
|
WHERE tsbc.train_schedule_id = ts.id AND c.deleted_at IS NULL) AS "totalContainers",
|
|
(SELECT count(*) FROM freight.cargoes cg
|
|
JOIN freight.train_schedule_bookings tsbg ON tsbg.booking_id = cg.booking_id AND tsbg.deleted_at IS NULL
|
|
WHERE tsbg.train_schedule_id = ts.id AND cg.deleted_at IS NULL) AS "totalCargoes"
|
|
FROM freight.train_schedules ts
|
|
LEFT JOIN freight.yards oy ON oy.id = ts.origin_station_id
|
|
LEFT JOIN freight.yards dy ON dy.id = ts.destination_station_id
|
|
WHERE ${where.join(' AND ')}
|
|
ORDER BY COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) DESC NULLS LAST`,
|
|
params,
|
|
);
|
|
|
|
return rows
|
|
.filter((r) => {
|
|
const direction = deriveTradeDirection(
|
|
{ country: r.originCountry },
|
|
{ country: r.destinationCountry },
|
|
);
|
|
return (
|
|
direction === 'EXPORT' &&
|
|
this.isDjiboutiPortDestination(`${r.destination ?? ''} ${r.destinationName ?? ''}`)
|
|
);
|
|
})
|
|
.map(({ originCountry: _oc, destinationCountry: _dc, destinationName: _dn, ...rest }) => ({
|
|
...rest,
|
|
totalBookings: Number(rest.totalBookings) || 0,
|
|
totalContainers: Number(rest.totalContainers) || 0,
|
|
totalCargoes: Number(rest.totalCargoes) || 0,
|
|
route: rest.origin || rest.destination ? `${rest.origin ?? '?'} -> ${rest.destination ?? '?'}` : null,
|
|
}));
|
|
}
|
|
|
|
/** Assigned export booking items for an arrived Djibouti-side export train. Read-only. */
|
|
async exportDjiboutiTrainDetail(scheduleId: string): Promise<ExportTrainItemRow[]> {
|
|
const rows: ExportTrainItemRow[] = await this.dataSource.query(
|
|
`WITH assigned AS (
|
|
SELECT b.id AS booking_id,
|
|
b.reference,
|
|
b.company_id,
|
|
company.name AS customer_name,
|
|
w.wagon_number,
|
|
tsw.sequence_no,
|
|
wba.allocated_weight_tons,
|
|
COALESCE(cgt.cargo_type_name, b.cargo_free_text) AS cargo_type,
|
|
b.cargo_total_weight_vgm AS booking_weight,
|
|
oy.code AS origin,
|
|
dy.code AS destination,
|
|
ts.train_number,
|
|
COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS arrival_time,
|
|
inv.id AS inventory_id,
|
|
COALESCE(inv.status, b.status) AS current_status
|
|
FROM freight.train_schedule_bookings tsb
|
|
JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
|
|
JOIN freight.bookings b ON b.id = tsb.booking_id AND b.deleted_at IS NULL
|
|
LEFT JOIN freight.wagon_booking_allocations wba ON wba.booking_id = b.id
|
|
AND wba.deleted_at IS NULL
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM freight.train_set_wagons tsw_match
|
|
WHERE tsw_match.id = wba.train_set_wagon_id
|
|
AND tsw_match.train_set_id = ts.train_set_id
|
|
AND tsw_match.deleted_at IS NULL
|
|
)
|
|
LEFT JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
|
|
AND tsw.train_set_id = ts.train_set_id
|
|
AND tsw.deleted_at IS NULL
|
|
LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id AND w.deleted_at IS NULL
|
|
LEFT JOIN freight.companies company ON company.id = b.company_id
|
|
LEFT JOIN freight.cargo_types cgt ON cgt.id = b.cargo_type_id
|
|
LEFT JOIN freight.yards oy ON oy.id = b.origin_yard_id
|
|
LEFT JOIN freight.yards dy ON dy.id = b.destination_yard_id
|
|
LEFT JOIN freight.warehouse_inventory inv ON inv.booking_id = b.id AND inv.deleted_at IS NULL
|
|
WHERE tsb.train_schedule_id = $1 AND tsb.deleted_at IS NULL
|
|
)
|
|
SELECT a.booking_id AS "bookingId",
|
|
a.reference AS "bookingReference",
|
|
a.company_id AS "customerId",
|
|
a.customer_name AS "customerName",
|
|
a.wagon_number AS "wagonNumber",
|
|
a.sequence_no AS "sequenceNo",
|
|
a.allocated_weight_tons AS "allocatedWeightTons",
|
|
'CONTAINER' AS "itemType",
|
|
c.id AS "itemId",
|
|
a.inventory_id AS "inventoryId",
|
|
c.container_number AS "containerNumber",
|
|
a.cargo_type AS "cargoType",
|
|
a.booking_weight AS "weight",
|
|
a.origin,
|
|
a.destination,
|
|
a.train_number AS "trainSchedule",
|
|
a.arrival_time AS "arrivalTime",
|
|
a.current_status AS "currentStatus"
|
|
FROM assigned a
|
|
JOIN freight.containers c ON c.booking_id = a.booking_id AND c.deleted_at IS NULL
|
|
UNION ALL
|
|
SELECT a.booking_id AS "bookingId",
|
|
a.reference AS "bookingReference",
|
|
a.company_id AS "customerId",
|
|
a.customer_name AS "customerName",
|
|
a.wagon_number AS "wagonNumber",
|
|
a.sequence_no AS "sequenceNo",
|
|
a.allocated_weight_tons AS "allocatedWeightTons",
|
|
'CARGO' AS "itemType",
|
|
cg.id AS "itemId",
|
|
a.inventory_id AS "inventoryId",
|
|
NULL AS "containerNumber",
|
|
COALESCE(cgt.cargo_type_name, a.cargo_type) AS "cargoType",
|
|
COALESCE(cg.weight, a.booking_weight) AS "weight",
|
|
a.origin,
|
|
a.destination,
|
|
a.train_number AS "trainSchedule",
|
|
a.arrival_time AS "arrivalTime",
|
|
a.current_status AS "currentStatus"
|
|
FROM assigned a
|
|
JOIN freight.cargoes cg ON cg.booking_id = a.booking_id AND cg.deleted_at IS NULL
|
|
LEFT JOIN freight.cargo_types cgt ON cgt.id = cg.cargo_type_id
|
|
UNION ALL
|
|
SELECT a.booking_id AS "bookingId",
|
|
a.reference AS "bookingReference",
|
|
a.company_id AS "customerId",
|
|
a.customer_name AS "customerName",
|
|
a.wagon_number AS "wagonNumber",
|
|
a.sequence_no AS "sequenceNo",
|
|
a.allocated_weight_tons AS "allocatedWeightTons",
|
|
CASE WHEN a.cargo_type ILIKE '%container%' THEN 'CONTAINER' ELSE 'CARGO' END AS "itemType",
|
|
a.inventory_id AS "itemId",
|
|
a.inventory_id AS "inventoryId",
|
|
NULL AS "containerNumber",
|
|
a.cargo_type AS "cargoType",
|
|
a.booking_weight AS "weight",
|
|
a.origin,
|
|
a.destination,
|
|
a.train_number AS "trainSchedule",
|
|
a.arrival_time AS "arrivalTime",
|
|
a.current_status AS "currentStatus"
|
|
FROM assigned a
|
|
WHERE NOT EXISTS (SELECT 1 FROM freight.containers c WHERE c.booking_id = a.booking_id AND c.deleted_at IS NULL)
|
|
AND NOT EXISTS (SELECT 1 FROM freight.cargoes cg WHERE cg.booking_id = a.booking_id AND cg.deleted_at IS NULL)
|
|
ORDER BY "sequenceNo" ASC NULLS LAST, "bookingReference" ASC NULLS LAST, "itemType" ASC`,
|
|
[scheduleId],
|
|
);
|
|
return rows;
|
|
}
|
|
}
|