Merge pull request #768 from Tria-plc/intercity-load-unload

feat(intercity): show intercity cargo across every train
This commit is contained in:
Hagernesh Tadesse
2026-07-17 15:00:15 +03:00
committed by GitHub
8 changed files with 422 additions and 0 deletions

View File

@@ -40,6 +40,68 @@ export class IntercityService {
* remaining capacity along all three axes (wagons, weight, length) and each
* booking's need, so staff can pick what fits.
*/
/**
* Every intercity booking and where it is in its ride-along, across all trains.
*
* The per-schedule candidate list answers "what can THIS train carry"; this
* answers "what is happening to intercity cargo" — which is what a yard
* operator needs when the work is spread over whichever trains happen to pass.
*
* Carries each end's facility state, because a booking whose origin or
* destination has no facility can never be loaded or unloaded there and the
* operator should see that before the train arrives, not when the load is
* refused.
*/
async listBookings() {
return this.dataSource.query(
`SELECT b.id AS "bookingId",
b.reference AS "reference",
b.status AS "status",
b.freight_type AS "freightType",
b.cargo_total_weight_vgm AS "weightTons",
b.loaded_at AS "loadedAt",
b.arrived_at AS "arrivedAt",
company.name AS "customer",
b.train_schedule_id AS "trainScheduleId",
ts.train_number AS "trainNumber",
ts.status AS "scheduleStatus",
oy.id AS "originYardId",
COALESCE(oy.label, oy.code) AS "origin",
oy.has_facility AS "originHasFacility",
dy.id AS "destinationYardId",
COALESCE(dy.label, dy.code) AS "destination",
dy.has_facility AS "destinationHasFacility",
-- Where the train actually is, so the operator knows if the cargo
-- can be worked right now.
cp.yard_id AS "trainAtYardId",
-- Most recent GRN raised for this booking at a facility.
fh.grn_number AS "grnNumber"
FROM freight.bookings b
LEFT JOIN freight.companies company ON company.id = b.company_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.train_schedules ts
ON ts.id = b.train_schedule_id AND ts.deleted_at IS NULL
LEFT JOIN LATERAL (
SELECT c.yard_id
FROM freight.train_checkpoint_events c
WHERE c.train_schedule_id = b.train_schedule_id
ORDER BY c.occurred_at DESC, c.created_at DESC
LIMIT 1
) cp ON true
LEFT JOIN LATERAL (
SELECT e.grn_number
FROM freight.facility_handling_events e
WHERE e.booking_id = b.id AND e.deleted_at IS NULL
ORDER BY e.occurred_at DESC
LIMIT 1
) fh ON true
WHERE b.deleted_at IS NULL
AND b.trade_direction = 'DOMESTIC'
ORDER BY b.created_at DESC`,
);
}
async listCandidates(scheduleId: string) {
const schedule = await this.getSchedule(scheduleId);
const milestoneSeq = await this.routeMilestoneSequence(schedule);

View File

@@ -459,6 +459,16 @@ export class TrainSchedulingController {
return this.trainSchedulingService.dispatchSchedule(id);
}
@Get("intercity/bookings")
@TrainSchedulingView()
@ApiOperation({
summary:
"Every intercity booking with its ride-along state, both yards' facility status, and where its train is",
})
listIntercityBookings() {
return this.intercityService.listBookings();
}
@Get("schedules/:id/intercity-candidates")
@TrainSchedulingView()
@ApiOperation({