fix issue

This commit is contained in:
Marshal
2026-07-16 00:33:31 +00:00
parent 234a74e812
commit 41fe04652f
51 changed files with 1895 additions and 206 deletions

View File

@@ -1,7 +1,7 @@
// apps/edr-freight-api/src/modules/container-management/containers.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { FindOptionsOrder, FindOptionsWhere, ILike, Repository } from 'typeorm';
import { DataSource, FindOptionsOrder, FindOptionsWhere, ILike, Repository } from 'typeorm';
import { CreateContainerDto } from './dto/create-container.dto';
import { UpdateContainerDto } from './dto/update-container.dto';
import { AssignContainerToWagonDto } from './dto/assign-container-to-wagon.dto';
@@ -18,6 +18,7 @@ export class ContainersService {
private readonly wagonRepo: Repository<Wagon>, // ✅ use raw repository
@InjectRepository(ContainerType)
private readonly containerTypeRepo: Repository<ContainerType>,
private readonly dataSource: DataSource,
) {}
async create(dto: CreateContainerDto): Promise<Container> {
@@ -115,24 +116,42 @@ export class ContainersService {
if (container.status === 'LOADED') {
throw new ConflictException('Cannot reassign a loaded container');
}
// Reject a container that is already placed on a wagon — it must be
// unassigned first, otherwise it would silently jump to another wagon.
if (container.wagonId) {
throw new ConflictException(
`Container ${containerId} is already assigned to wagon ${container.wagonId}`,
);
}
const wagon = await this.wagonRepo.findOne({ where: { id: dto.wagonId } });
if (!wagon) throw new NotFoundException(`Wagon ${dto.wagonId} not found`);
let position: number | null = dto.position ?? null;
if (position === null) {
const maxPos = await this.containerRepo
.createQueryBuilder('c')
.select('MAX(c.position)', 'max')
.where('c.wagonId = :wagonId', { wagonId: wagon.id })
.getRawOne();
position = (maxPos?.max ?? 0) + 1;
}
// The MAX(position)+1 allocation is check-then-act: two concurrent assigns can
// read the same MAX and collide on the same position. Do the read + save inside
// one transaction to narrow the race window.
// TODO: add a unique (wagon_id, position) DB index so the database itself
// rejects a colliding position even under concurrency.
return this.dataSource.transaction(async (manager) => {
const containerRepo = manager.getRepository(Container);
container.wagonId = wagon.id;
container.position = position;
container.status = 'AVAILABLE';
return this.containerRepo.save(container);
let position: number | null = dto.position ?? null;
if (position === null) {
const maxPos = await containerRepo
.createQueryBuilder('c')
.select('MAX(c.position)', 'max')
.where('c.wagonId = :wagonId', { wagonId: wagon.id })
.getRawOne<{ max: number | null }>();
position = (maxPos?.max ?? 0) + 1;
}
container.wagonId = wagon.id;
container.position = position;
// Placing a container on a wagon does not make it AVAILABLE. The status enum
// (AVAILABLE, LOADED, IN_TRANSIT, MAINTENANCE, DAMAGED) has no ASSIGNED/ON_WAGON
// state, so leave the existing status unchanged rather than forcing AVAILABLE.
return containerRepo.save(container);
});
}
async unassignFromWagon(containerId: string): Promise<Container> {