mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 19:58:11 +00:00
feat(freight): offer built-train wagons per boarding yard on multi-yard consists
This commit is contained in:
@@ -44,7 +44,7 @@ export class BuildTrainDto {
|
||||
@ApiPropertyOptional({
|
||||
type: [String],
|
||||
format: 'uuid',
|
||||
description: 'Wagons to attach at build time, in consist order (must sit in the same yard)',
|
||||
description: 'Wagons to attach at build time, in consist order (any yard)',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
|
||||
@@ -283,6 +283,10 @@ export class TrainBuilderService {
|
||||
wagonNumber: wagon.wagonNumber,
|
||||
sequenceNumber: wagon.sequenceNumber,
|
||||
status: wagon.status,
|
||||
currentYardId: wagon.currentYardId ?? null,
|
||||
currentYard: wagon.currentYard
|
||||
? { id: wagon.currentYard.id, code: wagon.currentYard.code, label: wagon.currentYard.label }
|
||||
: null,
|
||||
wagonType: wagon.wagonType
|
||||
? {
|
||||
id: wagon.wagonType.id,
|
||||
@@ -326,6 +330,27 @@ export class TrainBuilderService {
|
||||
: null,
|
||||
locomotives,
|
||||
wagons,
|
||||
// Where the consist physically stands. A train built from several yards
|
||||
// only picks a yard's wagons up when it reaches that yard, and a customer
|
||||
// boarding there can only book the wagons standing there — the schedule
|
||||
// route must therefore cover every one of these yards before its
|
||||
// destination.
|
||||
wagonYards: [
|
||||
...wagons
|
||||
.reduce((acc, wagon) => {
|
||||
const id = wagon.currentYardId ?? 'UNASSIGNED';
|
||||
const entry = acc.get(id) ?? {
|
||||
yardId: wagon.currentYardId ?? null,
|
||||
code: wagon.currentYard?.code ?? null,
|
||||
label: wagon.currentYard?.label ?? null,
|
||||
wagonCount: 0,
|
||||
};
|
||||
entry.wagonCount += 1;
|
||||
acc.set(id, entry);
|
||||
return acc;
|
||||
}, new Map<string, { yardId: string | null; code: string | null; label: string | null; wagonCount: number }>())
|
||||
.values(),
|
||||
].sort((a, b) => b.wagonCount - a.wagonCount),
|
||||
totals: {
|
||||
wagonCount: wagons.length,
|
||||
totalTareTons,
|
||||
@@ -428,10 +453,13 @@ export class TrainBuilderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Relocate the train to another yard. The consist moves as one unit: every
|
||||
* coupled locomotive and wagon follows to the new yard (so their current
|
||||
* yards always match the train's), and each wagon gets a movement-ledger row.
|
||||
* Blocked while the train is out on a dispatched run.
|
||||
* Relocate the train to another yard. The locomotives always follow. Of the
|
||||
* wagons, only those standing WITH the train move: on a consist spread
|
||||
* across yards (20 in Dire, 33 waiting in Mojo), moving the train Dire→Mojo
|
||||
* relocates the 20 it is actually pulling and leaves the Mojo wagons where
|
||||
* they stand — the train collects those by arriving, not by this call.
|
||||
* Each moved wagon gets a movement-ledger row. Blocked while the train is
|
||||
* out on a dispatched run.
|
||||
*/
|
||||
async setYard(id: string, currentYardId: string) {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
@@ -439,6 +467,7 @@ export class TrainBuilderService {
|
||||
if (train.currentYardId === currentYardId) return;
|
||||
const yard = await manager.getRepository(Yard).findOne({ where: { id: currentYardId } });
|
||||
if (!yard) throw new NotFoundException(`Yard ${currentYardId} not found`);
|
||||
const previousYardId = train.currentYardId ?? null;
|
||||
|
||||
await manager.getRepository(Train).update(train.id, { currentYardId: yard.id });
|
||||
|
||||
@@ -454,7 +483,17 @@ export class TrainBuilderService {
|
||||
);
|
||||
}
|
||||
|
||||
const wagons = await manager.getRepository(Wagon).find({ where: { trainId: train.id } });
|
||||
const allWagons = await manager
|
||||
.getRepository(Wagon)
|
||||
.find({ where: { trainId: train.id } });
|
||||
// Wagons travelling with the train = those at the yard it is leaving.
|
||||
// A yard-less wagon has no standing position of its own, so it follows.
|
||||
const wagons = allWagons.filter(
|
||||
(wagon) =>
|
||||
wagon.currentYardId == null ||
|
||||
previousYardId == null ||
|
||||
wagon.currentYardId === previousYardId,
|
||||
);
|
||||
const now = new Date();
|
||||
for (const wagon of wagons) {
|
||||
if (wagon.currentYardId === yard.id) continue;
|
||||
@@ -475,7 +514,7 @@ export class TrainBuilderService {
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/** Append AVAILABLE wagons from the train's own yard to the consist. */
|
||||
/** Append AVAILABLE, unassigned wagons (any yard) to the consist. */
|
||||
async assignWagons(id: string, dto: AssignTrainWagonsDto, userId?: string | null) {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const train = await this.getEditableTrain(manager, id);
|
||||
@@ -1037,11 +1076,8 @@ export class TrainBuilderService {
|
||||
if (wagon.status !== WagonStatus.Available) {
|
||||
throw new ConflictException(`Wagon ${wagon.wagonNumber} is not available (${wagon.status})`);
|
||||
}
|
||||
if (wagon.currentYardId !== train.currentYardId) {
|
||||
throw new BadRequestException(
|
||||
`Wagon ${wagon.wagonNumber} is not in the train's yard; only wagons in the same yard can be attached`,
|
||||
);
|
||||
}
|
||||
// Wagons may sit in any yard — the schedule's route must pass through
|
||||
// every wagon yard before its destination (checked at scheduling time).
|
||||
toAttach.push(wagon);
|
||||
}
|
||||
if (!toAttach.length) return [];
|
||||
|
||||
Reference in New Issue
Block a user