mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
train
This commit is contained in:
@@ -4,6 +4,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
|
||||
import { Yard } from '../../rule-engine/entities/yard.entity';
|
||||
import { Wagon } from './wagon.entity';
|
||||
import { WagonTransferRequest } from './wagon-transfer-request.entity';
|
||||
|
||||
/**
|
||||
* Ledger of every physical wagon relocation between yards — one row per move.
|
||||
@@ -51,6 +52,14 @@ export class WagonMovement extends BaseEntity {
|
||||
@Column({ name: 'moved_by_user_id', type: 'uuid', nullable: true })
|
||||
movedByUserId?: string | null;
|
||||
|
||||
/** The transfer request this move fulfilled, when it came from one. */
|
||||
@Column({ name: 'transfer_request_id', type: 'uuid', nullable: true })
|
||||
transferRequestId?: string | null;
|
||||
|
||||
@ManyToOne(() => WagonTransferRequest, { nullable: true })
|
||||
@JoinColumn({ name: 'transfer_request_id' })
|
||||
transferRequest?: WagonTransferRequest | null;
|
||||
|
||||
@Column({ name: 'occurred_at', type: 'timestamptz' })
|
||||
occurredAt!: Date;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
FleetManage,
|
||||
FleetView,
|
||||
WagonTransferFulfill,
|
||||
WagonTransferHistoryAll,
|
||||
WagonTransferRequest,
|
||||
} from '../../common/booking-guards';
|
||||
import { CreateTransferRequestDto } from './dto/create-transfer-request.dto';
|
||||
@@ -50,6 +51,30 @@ export class WagonTransferRequestsController {
|
||||
return this.service.listRequests(status);
|
||||
}
|
||||
|
||||
// NOTE: the two `history` routes MUST stay above `@Get(':id')` — Express
|
||||
// matches in declaration order, so `/history` would otherwise be captured by
|
||||
// the `:id` param route (and rejected by ParseUUIDPipe).
|
||||
@Get('history')
|
||||
@ApiOperation({
|
||||
summary: "Caller's own transfer history (requests filed/fulfilled + wagons moved)",
|
||||
})
|
||||
myHistory(@CurrentUser() user: TCurrentUser) {
|
||||
// Never fall through to the all-staff view: getHistory(undefined) means
|
||||
// "everyone", so a missing caller id must return empty, not leak scope.
|
||||
if (!user?.id) return { requests: [], movements: [] };
|
||||
return this.service.getHistory(user.id);
|
||||
}
|
||||
|
||||
@Get('history/all')
|
||||
@WagonTransferHistoryAll()
|
||||
@ApiQuery({ name: 'userId', required: false })
|
||||
@ApiOperation({
|
||||
summary: "Admin: any/all staff's transfer history (optional ?userId filter)",
|
||||
})
|
||||
allHistory(@Query('userId') userId?: string) {
|
||||
return this.service.getHistory(userId);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get one transfer request' })
|
||||
findOne(@Param('id', ParseUUIDPipe) id: string) {
|
||||
|
||||
@@ -6,14 +6,24 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { In, IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
import { CreateTransferRequestDto } from './dto/create-transfer-request.dto';
|
||||
import { FulfillTransferRequestDto } from './dto/fulfill-transfer-request.dto';
|
||||
import { Wagon } from './entities/wagon.entity';
|
||||
import { WagonMovement } from './entities/wagon-movement.entity';
|
||||
import { WagonTransferRequest } from './entities/wagon-transfer-request.entity';
|
||||
import { WagonsService } from './wagons.service';
|
||||
|
||||
/** Bundled per-user activity: requests they touched + wagons they moved. */
|
||||
export interface TransferHistory {
|
||||
requests: WagonTransferRequest[];
|
||||
movements: WagonMovement[];
|
||||
}
|
||||
|
||||
/** How many ledger rows the history returns at most (newest first). */
|
||||
const HISTORY_LIMIT = 500;
|
||||
|
||||
const REQUEST_RELATIONS = {
|
||||
fromYard: true,
|
||||
toYard: true,
|
||||
@@ -33,6 +43,8 @@ export class WagonTransferRequestsService {
|
||||
private readonly requestRepo: Repository<WagonTransferRequest>,
|
||||
@InjectRepository(Wagon)
|
||||
private readonly wagonRepo: Repository<Wagon>,
|
||||
@InjectRepository(WagonMovement)
|
||||
private readonly movementRepo: Repository<WagonMovement>,
|
||||
private readonly wagonsService: WagonsService,
|
||||
) {}
|
||||
|
||||
@@ -125,10 +137,12 @@ export class WagonTransferRequestsService {
|
||||
);
|
||||
}
|
||||
|
||||
// Reuse the audited bulk-transfer path (writes wagon_movements ledger rows).
|
||||
// Reuse the audited bulk-transfer path (writes wagon_movements ledger rows,
|
||||
// each stamped with this request's id so history can link them back).
|
||||
await this.wagonsService.bulkTransfer(
|
||||
{ wagonIds, toYardId: request.toYardId },
|
||||
userId,
|
||||
{ transferRequestId: request.id },
|
||||
);
|
||||
|
||||
request.status = WagonTransferRequestStatus.Fulfilled;
|
||||
@@ -138,6 +152,38 @@ export class WagonTransferRequestsService {
|
||||
return this.findById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-user transfer history: the requests a user filed OR fulfilled, plus the
|
||||
* individual wagons they physically moved (linked back to their request when
|
||||
* one drove the move). Pass a `userId` to scope to one staffer; pass
|
||||
* `undefined` for the admin all-staff view. Scope is decided by the CALLER
|
||||
* (the controller passes the caller's id unless they hold the history-all
|
||||
* permission) — this method trusts its argument.
|
||||
*/
|
||||
async getHistory(userId?: string | null): Promise<TransferHistory> {
|
||||
const requests = await this.requestRepo.find({
|
||||
where: userId
|
||||
? [{ requestedByUserId: userId }, { fulfilledByUserId: userId }]
|
||||
: {},
|
||||
relations: REQUEST_RELATIONS,
|
||||
order: { createdAt: 'DESC' },
|
||||
take: HISTORY_LIMIT,
|
||||
});
|
||||
|
||||
const movements = await this.movementRepo.find({
|
||||
// Own view: moves I made. All view: every user-attributed move (skip the
|
||||
// system-written loaded/reposition legs that carry no mover).
|
||||
where: userId
|
||||
? { movedByUserId: userId }
|
||||
: { movedByUserId: Not(IsNull()) },
|
||||
relations: { wagon: true, fromYard: true, toYard: true, transferRequest: true },
|
||||
order: { occurredAt: 'DESC' },
|
||||
take: HISTORY_LIMIT,
|
||||
});
|
||||
|
||||
return { requests, movements };
|
||||
}
|
||||
|
||||
/** Withdraw a still-PENDING request. */
|
||||
async cancelRequest(id: string): Promise<WagonTransferRequest> {
|
||||
const request = await this.findById(id);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Wagon } from './entities/wagon.entity';
|
||||
import { WagonMovement } from './entities/wagon-movement.entity';
|
||||
import { WagonTransferRequest } from './entities/wagon-transfer-request.entity';
|
||||
import { Train } from '../trains/entities/train.entity';
|
||||
import { Yard } from '../rule-engine/entities/yard.entity';
|
||||
@@ -10,7 +11,15 @@ import { WagonsService } from './wagons.service';
|
||||
import { WagonTransferRequestsService } from './wagon-transfer-requests.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Wagon, WagonTransferRequest, Train, Yard])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Wagon,
|
||||
WagonMovement,
|
||||
WagonTransferRequest,
|
||||
Train,
|
||||
Yard,
|
||||
]),
|
||||
],
|
||||
controllers: [
|
||||
WagonsController,
|
||||
TrainWagonsReorderController,
|
||||
|
||||
@@ -180,6 +180,7 @@ export class WagonsService {
|
||||
async bulkTransfer(
|
||||
dto: BulkTransferWagonsDto,
|
||||
userId?: string | null,
|
||||
opts?: { transferRequestId?: string | null },
|
||||
): Promise<{ moved: number }> {
|
||||
const { wagonIds, toYardId } = dto;
|
||||
if (!wagonIds.length) return { moved: 0 };
|
||||
@@ -215,6 +216,7 @@ export class WagonsService {
|
||||
toYardId,
|
||||
kind: WagonMovementKind.Manual,
|
||||
movedByUserId: userId ?? null,
|
||||
transferRequestId: opts?.transferRequestId ?? null,
|
||||
occurredAt: new Date(),
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user