mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
- Direction derived from route via existing deriveTradeDirection (eligible-bookings, bulk receive guard, getBookingDirection) instead of stored trade_direction - Export tab sub-tabs (Receive Queue + Ready-to-Load/Loaded/Dispatch placeholders) - Receive Queue: full column set + per-row Receive; eligible-bookings adds customerId - create/update warehouse: map DB errors to 400; dashboard: distinct per-status colors Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
|
import { FindManyOptions, ILike, QueryFailedError } from 'typeorm';
|
|
|
|
import { CreateWarehouseDto } from './dto/create-warehouse.dto';
|
|
import { FilterWarehouseDto } from './dto/filter-warehouse.dto';
|
|
import { UpdateWarehouseDto } from './dto/update-warehouse.dto';
|
|
import { Warehouse } from './entities/warehouse.entity';
|
|
import { WarehousesRepository } from './warehouses.repository';
|
|
|
|
@Injectable()
|
|
export class WarehousesService {
|
|
constructor(private readonly warehousesRepository: WarehousesRepository) {}
|
|
|
|
async findAll(filter: FilterWarehouseDto): Promise<Warehouse[]> {
|
|
const where: FindManyOptions<Warehouse>['where'] = {
|
|
...(filter.type ? { type: filter.type } : {}),
|
|
...(filter.stationId ? { stationId: filter.stationId } : {}),
|
|
...(filter.status ? { status: filter.status } : {}),
|
|
};
|
|
|
|
const search = filter.search?.trim();
|
|
const whereClauses = search
|
|
? [
|
|
{ ...where, name: ILike(`%${search}%`) },
|
|
{ ...where, code: ILike(`%${search}%`) },
|
|
{ ...where, locationName: ILike(`%${search}%`) },
|
|
]
|
|
: where;
|
|
|
|
return this.warehousesRepository.findAll({
|
|
where: whereClauses,
|
|
relations: { facility: true },
|
|
order: { code: 'ASC' },
|
|
});
|
|
}
|
|
|
|
async findById(id: string): Promise<Warehouse> {
|
|
const warehouse = await this.warehousesRepository.findById(id, {
|
|
relations: { facility: true, yards: { zones: true } },
|
|
});
|
|
|
|
if (!warehouse) {
|
|
throw new NotFoundException(`Warehouse ${id} not found`);
|
|
}
|
|
|
|
return warehouse;
|
|
}
|
|
|
|
async create(dto: CreateWarehouseDto): Promise<Warehouse> {
|
|
await this.assertCodeUnique(dto.code.trim());
|
|
|
|
try {
|
|
return await this.warehousesRepository.create({
|
|
name: dto.name.trim(),
|
|
code: dto.code.trim(),
|
|
type: dto.type,
|
|
stationId: dto.stationId ?? null,
|
|
facilityId: dto.facilityId ?? null,
|
|
locationName: dto.locationName?.trim() ?? null,
|
|
capacityWeight: dto.capacityWeight ?? null,
|
|
capacityContainers: dto.capacityContainers ?? null,
|
|
maxWeight: dto.maxWeight ?? dto.capacityWeight ?? null,
|
|
maxVolume: dto.maxVolume ?? null,
|
|
currentWeight: 0,
|
|
currentContainers: 0,
|
|
currentVolume: 0,
|
|
status: 'ACTIVE',
|
|
isActive: true,
|
|
});
|
|
} catch (error) {
|
|
this.mapDbError(error);
|
|
}
|
|
}
|
|
|
|
async update(id: string, dto: UpdateWarehouseDto): Promise<Warehouse> {
|
|
const existing = await this.findById(id);
|
|
|
|
if (dto.code && dto.code.trim() !== existing.code) {
|
|
await this.assertCodeUnique(dto.code.trim(), id);
|
|
}
|
|
|
|
const status = dto.status ?? existing.status;
|
|
|
|
let updated;
|
|
try {
|
|
updated = await this.warehousesRepository.update(id, {
|
|
name: dto.name?.trim() ?? existing.name,
|
|
code: dto.code?.trim() ?? existing.code,
|
|
type: dto.type ?? existing.type,
|
|
stationId: dto.stationId ?? existing.stationId,
|
|
facilityId: dto.facilityId ?? existing.facilityId,
|
|
locationName: dto.locationName?.trim() ?? existing.locationName,
|
|
capacityWeight: dto.capacityWeight ?? existing.capacityWeight,
|
|
capacityContainers: dto.capacityContainers ?? existing.capacityContainers,
|
|
maxWeight: dto.maxWeight ?? existing.maxWeight,
|
|
maxVolume: dto.maxVolume ?? existing.maxVolume,
|
|
status,
|
|
isActive: status === 'ACTIVE',
|
|
});
|
|
} catch (error) {
|
|
this.mapDbError(error);
|
|
}
|
|
|
|
if (!updated) {
|
|
throw new NotFoundException(`Warehouse ${id} not found`);
|
|
}
|
|
|
|
return this.findById(id);
|
|
}
|
|
|
|
/** Map low-level DB errors (FK / length / etc.) to a clean 400 instead of a 500. */
|
|
private mapDbError(error: unknown): never {
|
|
if (error instanceof QueryFailedError) {
|
|
const driver = (error as QueryFailedError & { driverError?: { code?: string; detail?: string } }).driverError;
|
|
if (driver?.code === '23503') {
|
|
throw new BadRequestException('Selected facility does not exist.');
|
|
}
|
|
if (driver?.code === '22001') {
|
|
throw new BadRequestException('A field is too long (code max 40, name max 160 characters).');
|
|
}
|
|
throw new BadRequestException(driver?.detail ?? error.message ?? 'Invalid warehouse data.');
|
|
}
|
|
throw error as Error;
|
|
}
|
|
|
|
private async assertCodeUnique(code: string, ignoreId?: string): Promise<void> {
|
|
const [existing] = await this.warehousesRepository.findAll({ where: { code } });
|
|
|
|
if (existing && existing.id !== ignoreId) {
|
|
throw new ConflictException(`Warehouse code ${code} already exists`);
|
|
}
|
|
}
|
|
}
|