mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
Merge pull request #1460 from Tria-plc/Wrehousechanges
feat(train-scheduling): per-row Departure/Arrival Station on marshall…
This commit is contained in:
@@ -1242,6 +1242,44 @@ describe('TrainSchedulingService', () => {
|
||||
expect(withoutChanges).not.toContain('Consist Changed At This Stop');
|
||||
});
|
||||
|
||||
it('shows per-row Departure/Arrival Station — schedule endpoints for a whole-route wagon, its own board/alight yard for a leg slot', () => {
|
||||
const wholeRoute = { ...makeWagon(1, 'W-001', [loadedAllocation]), id: 'slot-1' };
|
||||
const legSlot = {
|
||||
...makeWagon(2, 'W-LEG', [loadedAllocation]),
|
||||
id: 'slot-leg',
|
||||
boardYardId: 'yard-dire',
|
||||
alightYardId: 'yard-adama',
|
||||
};
|
||||
const schedule = {
|
||||
id: 'schedule-1',
|
||||
trainNumber: '8302',
|
||||
direction: 'EXPORT',
|
||||
originStation: { label: 'DCT/SGTD' },
|
||||
destinationStation: { label: 'GMP (Gelan Multipurpose Port)' },
|
||||
trainSet: { wagons: [wholeRoute, legSlot] },
|
||||
scheduleBookings: [],
|
||||
};
|
||||
const build = (service as never as {
|
||||
buildExportLoadListHtml: (s: unknown, o?: unknown) => string;
|
||||
}).buildExportLoadListHtml.bind(service);
|
||||
|
||||
const html = build(schedule, {
|
||||
yardLabelById: new Map([
|
||||
['yard-dire', 'Dire Dawa Port'],
|
||||
['yard-adama', 'Adama'],
|
||||
]),
|
||||
});
|
||||
|
||||
expect(html).toContain('<th>Departure Station</th>');
|
||||
expect(html).toContain('<th>Arrival Station</th>');
|
||||
// Whole-route wagon: schedule's own endpoints.
|
||||
expect(html).toContain('<td>DCT/SGTD</td>');
|
||||
expect(html).toContain('<td>GMP (Gelan Multipurpose Port)</td>');
|
||||
// Leg slot: its own board/alight yard, not the schedule's endpoints.
|
||||
expect(html).toContain('<td>Dire Dawa Port</td>');
|
||||
expect(html).toContain('<td>Adama</td>');
|
||||
});
|
||||
|
||||
it('lists loaded empty containers by number and states they are empty', () => {
|
||||
const schedule = {
|
||||
id: 'schedule-1',
|
||||
|
||||
@@ -3557,17 +3557,19 @@ export class TrainSchedulingService {
|
||||
|
||||
// Leg slots couple mid-corridor — this origin document must say where their
|
||||
// cargo boards instead of listing it as loaded here (see the import list).
|
||||
const slotYardLabels = await this.yardLabelsById(
|
||||
(schedule.trainSet?.wagons ?? []).map((wagon) => wagon.boardYardId),
|
||||
// Also doubles as the per-row Departure/Arrival Station lookup below.
|
||||
const yardLabelById = await this.yardLabelsById(
|
||||
(schedule.trainSet?.wagons ?? []).flatMap((wagon) => [wagon.boardYardId, wagon.alightYardId]),
|
||||
);
|
||||
const pendingBoardYardLabelBySlot = new Map(
|
||||
(schedule.trainSet?.wagons ?? [])
|
||||
.filter((wagon) => wagon.boardYardId)
|
||||
.map((wagon) => [wagon.id, slotYardLabels.get(wagon.boardYardId!) ?? 'en route']),
|
||||
.map((wagon) => [wagon.id, yardLabelById.get(wagon.boardYardId!) ?? 'en route']),
|
||||
);
|
||||
|
||||
const html = this.buildExportLoadListHtml(schedule, {
|
||||
pendingBoardYardLabelBySlot,
|
||||
yardLabelById,
|
||||
emptyContainers: await this.loadedEmptyContainers(scheduleId),
|
||||
logoImageUrl: await this.logoSettings.getLogoImageUrl(),
|
||||
});
|
||||
@@ -3761,6 +3763,12 @@ export class TrainSchedulingService {
|
||||
where: { trainScheduleId: scheduleId, yardId: stop.yardId },
|
||||
order: { occurredAt: 'ASC' },
|
||||
});
|
||||
// Per-row Departure/Arrival Station: a whole-route wagon reads the
|
||||
// schedule's own origin/destination, a leg-slot wagon reads where IT
|
||||
// boards/alights instead.
|
||||
const yardLabelById = await this.yardLabelsById(
|
||||
(schedule.trainSet?.wagons ?? []).flatMap((wagon) => [wagon.boardYardId, wagon.alightYardId]),
|
||||
);
|
||||
const html = this.buildExportLoadListHtml(schedule, {
|
||||
title: `Intercity Marshalling Document / Load List (Marshalling ${stopIndex})`,
|
||||
positionLabel: `At ${stop.yardLabel}`,
|
||||
@@ -3769,6 +3777,7 @@ export class TrainSchedulingService {
|
||||
emptyContainers: await this.loadedEmptyContainers(scheduleId),
|
||||
logoImageUrl: await this.logoSettings.getLogoImageUrl(),
|
||||
consistChangesAtStop: this.consistChangesAt(schedule, logRows),
|
||||
yardLabelById,
|
||||
});
|
||||
// Styled table-aware fallback (marshalling grid) — see importLoadListDocument.
|
||||
const buffer = await this.pdfDocuments.renderTabularDocument(html, `Marshalling ${stopIndex}`);
|
||||
@@ -3808,6 +3817,9 @@ export class TrainSchedulingService {
|
||||
? `After ${last.yard?.label ?? last.yard?.code ?? 'checkpoint'}`
|
||||
: `At ${schedule.originStation?.label ?? schedule.originStation?.code ?? 'origin'} — no checkpoint recorded`;
|
||||
const { wagons, unassignedBookings } = this.intercityOnBoardView(schedule);
|
||||
const yardLabelById = await this.yardLabelsById(
|
||||
(schedule.trainSet?.wagons ?? []).flatMap((wagon) => [wagon.boardYardId, wagon.alightYardId]),
|
||||
);
|
||||
const html = this.buildExportLoadListHtml(schedule, {
|
||||
title: 'Intercity Marshalling Document / Load List (Marshalling 2)',
|
||||
positionLabel,
|
||||
@@ -3815,6 +3827,7 @@ export class TrainSchedulingService {
|
||||
unassignedBookings,
|
||||
emptyContainers: await this.loadedEmptyContainers(scheduleId),
|
||||
logoImageUrl: await this.logoSettings.getLogoImageUrl(),
|
||||
yardLabelById,
|
||||
});
|
||||
// Styled table-aware fallback (marshalling grid) — see importLoadListDocument.
|
||||
const buffer = await this.pdfDocuments.renderTabularDocument(html, 'Intercity marshalling / load list');
|
||||
@@ -3884,6 +3897,10 @@ export class TrainSchedulingService {
|
||||
// Slots that couple to the train downstream (slot id → board yard label).
|
||||
// Their cargo renders as TO LOAD AT and stays out of the loaded tallies.
|
||||
pendingBoardYardLabelBySlot?: Map<string, string>;
|
||||
// yardId → label, for the per-row Departure/Arrival Station columns
|
||||
// (falls back to the schedule's own origin/destination when a wagon's
|
||||
// boardYardId/alightYardId is null — i.e. it rides the whole corridor).
|
||||
yardLabelById?: Map<string, string>;
|
||||
// Numbered marshalling docs only (see marshallingDocumentAt /
|
||||
// consistChangesAt) — couples/uncouples/switches logged at THIS stop.
|
||||
// Origin import/export docs never pass this, so they render no such box.
|
||||
@@ -3924,15 +3941,28 @@ export class TrainSchedulingService {
|
||||
empty,
|
||||
]);
|
||||
}
|
||||
const originLabel = schedule.originStation?.label ?? schedule.originStation?.code;
|
||||
const destinationLabel = schedule.destinationStation?.label ?? schedule.destinationStation?.code;
|
||||
const rows = wagons
|
||||
.flatMap((wagon) => {
|
||||
// Departure/Arrival Station per row: a leg-slot wagon boards/alights
|
||||
// somewhere other than the schedule's own endpoints; a whole-route
|
||||
// wagon just reads origin/destination.
|
||||
const departureLabel = wagon.boardYardId
|
||||
? (opts?.yardLabelById?.get(wagon.boardYardId) ?? 'en route')
|
||||
: originLabel;
|
||||
const arrivalLabel = wagon.alightYardId
|
||||
? (opts?.yardLabelById?.get(wagon.alightYardId) ?? 'en route')
|
||||
: destinationLabel;
|
||||
// Wagon identity is the same on every row the wagon produces, loaded or not.
|
||||
const wagonCells = `<td>${esc(wagon.sequenceNo)}</td>
|
||||
<td>${esc(wagon.physicalWagon?.wagonNumber)}</td>
|
||||
<td>${esc(wagon.wagonType?.code ?? wagon.wagonType?.name)}</td>
|
||||
<td class="num">${esc(Number(wagon.lengthMeters || 0).toFixed(3))}</td>
|
||||
<td class="num">${esc(Number(wagon.wagonType?.tareWeightTons ?? 0).toFixed(2))}</td>
|
||||
<td class="num">${esc(Number(wagon.capacityTons || 0).toFixed(3))}</td>`;
|
||||
<td class="num">${esc(Number(wagon.capacityTons || 0).toFixed(3))}</td>
|
||||
<td>${esc(departureLabel)}</td>
|
||||
<td>${esc(arrivalLabel)}</td>`;
|
||||
const allocations = wagon.allocations ?? [];
|
||||
// An empty wagon still runs in the consist, so it still gets a line. Staff
|
||||
// check this document against the physical train — a wagon with no row
|
||||
@@ -3956,7 +3986,7 @@ export class TrainSchedulingService {
|
||||
return [
|
||||
`<tr class="empty">
|
||||
${wagonCells}
|
||||
<td colspan="4">EMPTY — no cargo allocated</td>
|
||||
<td colspan="5">EMPTY — no cargo allocated</td>
|
||||
</tr>`,
|
||||
];
|
||||
}
|
||||
@@ -3984,7 +4014,7 @@ export class TrainSchedulingService {
|
||||
// they are still physically on the train, so they get rows of their own.
|
||||
const unassigned = opts?.unassignedBookings ?? [];
|
||||
const unassignedRows = unassigned.length
|
||||
? `<tr class="empty"><td colspan="11">ON BOARD — WAGON NOT RECORDED</td></tr>` +
|
||||
? `<tr class="empty"><td colspan="13">ON BOARD — WAGON NOT RECORDED</td></tr>` +
|
||||
unassigned
|
||||
.map((booking) => {
|
||||
const containerNumbers = (booking.bookingContainers ?? [])
|
||||
@@ -3993,7 +4023,7 @@ export class TrainSchedulingService {
|
||||
.join(', ');
|
||||
const leg = `${booking.originYard?.label ?? booking.originYard?.code ?? '-'} → ${booking.destinationYard?.label ?? booking.destinationYard?.code ?? '-'}`;
|
||||
return `<tr>
|
||||
<td colspan="6">${esc(booking.reference)} — ${esc(leg)}</td>
|
||||
<td colspan="8">${esc(booking.reference)} — ${esc(leg)}</td>
|
||||
<td>${esc(booking.cargoType?.cargoTypeName ?? booking.cargoType?.code)}</td>
|
||||
<td>${esc(booking.company?.name)}</td>
|
||||
<td>${esc(containerNumbers)}</td>
|
||||
@@ -4134,6 +4164,8 @@ export class TrainSchedulingService {
|
||||
<th class="num">Equated Length</th>
|
||||
<th class="num">Tare Weight</th>
|
||||
<th class="num">Load Capacity</th>
|
||||
<th>Departure Station</th>
|
||||
<th>Arrival Station</th>
|
||||
<th>Cargo Type</th>
|
||||
<th>Company</th>
|
||||
<th>Container No</th>
|
||||
@@ -4142,7 +4174,7 @@ export class TrainSchedulingService {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows || '<tr><td colspan="10">No wagons on this train set.</td></tr>'}
|
||||
${rows || '<tr><td colspan="13">No wagons on this train set.</td></tr>'}
|
||||
${unassignedRows}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user