mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 20:05:41 +00:00
wagon work space, container validation
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { WagonStatus } from '@edr/types';
|
||||
import { ArrayNotEmpty, IsArray, IsEnum, IsUUID } from 'class-validator';
|
||||
|
||||
export class BulkSetWagonStatusDto {
|
||||
@IsArray()
|
||||
@ArrayNotEmpty()
|
||||
@IsUUID('4', { each: true })
|
||||
wagonIds!: string[];
|
||||
|
||||
@IsEnum(WagonStatus)
|
||||
status!: WagonStatus;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ArrayNotEmpty, IsArray, IsUUID } from 'class-validator';
|
||||
|
||||
export class BulkTransferWagonsDto {
|
||||
@IsArray()
|
||||
@ArrayNotEmpty()
|
||||
@IsUUID('4', { each: true })
|
||||
wagonIds!: string[];
|
||||
|
||||
@IsUUID()
|
||||
toYardId!: string;
|
||||
}
|
||||
@@ -10,12 +10,16 @@ import {
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentUser } from '@edr/api-common';
|
||||
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
|
||||
import { FleetManage, FleetView } from '../../common/booking-guards';
|
||||
import { CreateWagonDto } from './dto/create-wagon.dto';
|
||||
import { ListWagonsQueryDto } from './dto/list-wagons-query.dto';
|
||||
import { UpdateWagonDto } from './dto/update-wagon.dto';
|
||||
import { AssignWagonToTrainDto } from './dto/assign-wagon-to-train.dto';
|
||||
import { ReorderWagonsDto } from './dto/reorder-wagons.dto';
|
||||
import { BulkTransferWagonsDto } from './dto/bulk-transfer-wagons.dto';
|
||||
import { BulkSetWagonStatusDto } from './dto/bulk-set-wagon-status.dto';
|
||||
import { WagonsService } from './wagons.service';
|
||||
|
||||
@ApiTags('wagons')
|
||||
@@ -78,6 +82,20 @@ export class WagonsController {
|
||||
unassignFromTrain(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.wagonsService.unassignFromTrain(id);
|
||||
}
|
||||
|
||||
@Post('bulk-transfer')
|
||||
@FleetManage()
|
||||
@ApiOperation({ summary: 'Transfer multiple wagons to a destination yard' })
|
||||
bulkTransfer(@Body() dto: BulkTransferWagonsDto, @CurrentUser() user: TCurrentUser) {
|
||||
return this.wagonsService.bulkTransfer(dto, user?.id);
|
||||
}
|
||||
|
||||
@Post('bulk-status')
|
||||
@FleetManage()
|
||||
@ApiOperation({ summary: 'Set the status of multiple wagons' })
|
||||
bulkSetStatus(@Body() dto: BulkSetWagonStatusDto) {
|
||||
return this.wagonsService.bulkSetStatus(dto);
|
||||
}
|
||||
}
|
||||
|
||||
// Separate controller for train‑specific reorder (registered in module)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { WagonMovementKind, WagonStatus } from '@edr/types';
|
||||
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource, FindOptionsOrder, FindOptionsWhere, ILike } from 'typeorm';
|
||||
import { Repository, DataSource, FindOptionsOrder, FindOptionsWhere, ILike, In } from 'typeorm';
|
||||
import { CreateWagonDto } from './dto/create-wagon.dto';
|
||||
import { ListWagonsQueryDto } from './dto/list-wagons-query.dto';
|
||||
import { UpdateWagonDto } from './dto/update-wagon.dto';
|
||||
import { AssignWagonToTrainDto } from './dto/assign-wagon-to-train.dto';
|
||||
import { ReorderWagonsDto } from './dto/reorder-wagons.dto';
|
||||
import { BulkTransferWagonsDto } from './dto/bulk-transfer-wagons.dto';
|
||||
import { BulkSetWagonStatusDto } from './dto/bulk-set-wagon-status.dto';
|
||||
import { Wagon } from './entities/wagon.entity';
|
||||
import { WagonMovement } from './entities/wagon-movement.entity';
|
||||
import { Train } from '../trains/entities/train.entity';
|
||||
import { Yard } from '../rule-engine/entities/yard.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WagonsService {
|
||||
@@ -168,6 +171,101 @@ export class WagonsService {
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relocate many wagons to one destination yard in a single transaction. Each
|
||||
* wagon whose yard actually changes gets a `wagon_movements` ledger row (kind
|
||||
* `Manual`) so the yard history stays auditable — mirrors the single-wagon
|
||||
* `update` path. Wagons already in the destination yard are skipped.
|
||||
*/
|
||||
async bulkTransfer(
|
||||
dto: BulkTransferWagonsDto,
|
||||
userId?: string | null,
|
||||
): Promise<{ moved: number }> {
|
||||
const { wagonIds, toYardId } = dto;
|
||||
if (!wagonIds.length) return { moved: 0 };
|
||||
|
||||
const yard = await this.dataSource
|
||||
.getRepository(Yard)
|
||||
.findOne({ where: { id: toYardId } });
|
||||
if (!yard) throw new NotFoundException('Destination yard not found');
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
try {
|
||||
const wagons = await queryRunner.manager.find(Wagon, {
|
||||
where: { id: In(wagonIds) },
|
||||
});
|
||||
if (wagons.length !== wagonIds.length) {
|
||||
throw new NotFoundException('One or more wagons not found');
|
||||
}
|
||||
|
||||
let moved = 0;
|
||||
for (const wagon of wagons) {
|
||||
const previousYardId = wagon.currentYardId ?? null;
|
||||
if (previousYardId === toYardId) continue;
|
||||
wagon.currentYardId = toYardId;
|
||||
// Drop the eager relation so the scalar FK wins on save (see `update`).
|
||||
wagon.currentYard = null;
|
||||
await queryRunner.manager.save(Wagon, wagon);
|
||||
await queryRunner.manager.save(
|
||||
queryRunner.manager.create(WagonMovement, {
|
||||
wagonId: wagon.id,
|
||||
fromYardId: previousYardId,
|
||||
toYardId,
|
||||
kind: WagonMovementKind.Manual,
|
||||
movedByUserId: userId ?? null,
|
||||
occurredAt: new Date(),
|
||||
}),
|
||||
);
|
||||
moved++;
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return { moved };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the same status on many wagons in one transaction (e.g. flip a batch
|
||||
* from Available to Assigned in the yard workspace). Only the `status` column
|
||||
* is touched — train assignment is managed through the assign/unassign flow.
|
||||
*/
|
||||
async bulkSetStatus(dto: BulkSetWagonStatusDto): Promise<{ updated: number }> {
|
||||
const { wagonIds, status } = dto;
|
||||
if (!wagonIds.length) return { updated: 0 };
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
try {
|
||||
const wagons = await queryRunner.manager.find(Wagon, {
|
||||
where: { id: In(wagonIds) },
|
||||
});
|
||||
if (wagons.length !== wagonIds.length) {
|
||||
throw new NotFoundException('One or more wagons not found');
|
||||
}
|
||||
|
||||
for (const wagon of wagons) {
|
||||
wagon.status = status;
|
||||
}
|
||||
await queryRunner.manager.save(Wagon, wagons);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return { updated: wagons.length };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
async reorderWagons(_trainId: string, dto: ReorderWagonsDto): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
|
||||
Reference in New Issue
Block a user