mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 22:30:55 +00:00
feat(intercity): show intercity cargo across every train
Intercity bookings never get their own train — they ride whichever import/export train passes through their corridor — so the work is scattered across other people's schedules and there was nowhere to see it as a whole. The per-schedule ride-along panel answers "what can THIS train carry"; this answers "what is happening to intercity cargo". Purely additive: the existing ride-along panel and the schedule detail page are untouched, and loading/unloading still happens there, where the train's position is confirmed. This is a read-only view that points back to it. Each row carries both ends' facility status, because a booking whose origin or destination has no equipment can never be worked there — the operator should see that while the train is still coming, not when the load is refused. Those bookings are counted and called out. New GET /train-scheduling/intercity/bookings; the type is IntercityRideAlongRow, not IntercityBookingRow, which already means the per-schedule candidate row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user